#!/bin/bash

# ------------------------------------
# 配置、变量获取、初始化类
#
# 读取设置文件
get_config() {
	while [[ "$*" != "" ]]; do
		eval "${1}='$(uci get wechatpush.config.$1 2>/dev/null)'"
		shift
	done
}

# 后台读取 json 中的变量
read_json() {
	local json_key="$1"
	local json_path="$2"
	local output_file="$output_dir/${json_key}"
	jq -r ."$json_key" "$json_path" >"$output_file" &
}

# 遍历输出目录，将文件内容保存到对应的变量
wait_and_cat() {
	[ $(ls -A "$output_dir" | wc -l) -eq 0 ] && return
	wait
	for file in "$output_dir"/*; do
		local variable_name=$(basename "$file")
		local variable_value=$(cat "$file")
		eval "${variable_name}='${variable_value}'"
	done
	if [ ! -f "${dir}/send_pid" ]; then
		rm "$output_dir"/*
	fi
}

# 初始化设置信息
read_config() {
	get_config \
		"enable" "lite_enable" "device_name" "proxy_address" "sleeptime" "oui_data" "reset_regularly" "debuglevel" \
		"jsonpath" "sckey" "corpid" "userid" "agentid" "corpsecret" "mediapath" "wxpusher_apptoken" "wxpusher_uids" "wxpusher_topicIds" "pushplus_token" "tg_token" "chat_id" \
		"get_ipv4_mode" "ipv4_interface" "get_ipv6_mode" "ipv6_interface" \
		"device_notification" "cpu_notification" "cpu_load_threshold" "temperature_threshold" \
		"client_usage" "client_usage_max" "client_usage_disturb" "client_usage_whitelist" \
		"login_notification" "login_max_num" "login_web_black" "login_ip_black_timeout" "login_ip_white_list" "port_knocking_enable" "login_ip_white_timeout" "login_port_white" "login_port_forward_list" \
		"crontab_regular_time" "crontab_interval_time" \
		"do_not_disturb_mode" "do_not_disturb_starttime" "do_not_disturb_endtime" "up_down_push_whitelist" "up_down_push_blacklist" "up_down_push_interface" "mac_online_list" "mac_offline_list" "cpu_threshold_duration" "cpu_notification_delay" "login_disturb" "login_notification_delay" "login_log_enable" \
		"up_timeout" "down_timeout" "timeout_retry_count" "always_check_ip_list" "only_timeout_push" "passive_mode" "thread_num" "defaultSortColumn" "soc_code" "server_host" "server_port" \
		"unattended_enable" "zerotier_helper" "unattended_only_on_disturb_time" "unattended_device_aliases" "network_disconnect_event" "unattended_autoreboot_mode" "autoreboot_system_uptime" "autoreboot_network_uptime" \
		"device_info_helper" "gateway_host_url" "gateway_info_url" "gateway_logout_url" "gateway_username_id" "gateway_password_id" "gateway_username" "gateway_password" "scan_ip_range" "device_info_helper_sleeptime"

	(echo "$device_notification" | grep -q "online") && notification_online="true"
	(echo "$device_notification" | grep -q "offline") && notification_offline="true"
	(echo "$cpu_notification" | grep -q "load") && notification_load="true"
	(echo "$cpu_notification" | grep -q "temp") && notification_temp="true"
	(echo "$login_notification" | grep -q "web_logged") && web_logged="true"
	(echo "$login_notification" | grep -q "ssh_logged") && ssh_logged="true"
	(echo "$login_notification" | grep -q "web_login_failed") && web_login_failed="true"
	(echo "$login_notification" | grep -q "ssh_login_failed") && ssh_login_failed="true"
	(echo "$device_info_helper" | grep -q "gateway_info") && gateway_info_enable="true"
	for str_version in "wrtbwmon" "iputils-arping" "curl" "iw"; do
		eval $(echo ${str_version:0:2}"_version")=$(opkg list-installed | grep -w ^${str_version} | awk '{print $3}') 2>/dev/null
	done
	(opkg list-installed | grep -w -q "^firewall4") && nftables_version="true"
	devices_json="${dir}/devices.json"
	tmp_devices_json="${dir}/devices.json.tmp"
	tempjsonpath="${dir}/temp.json"
	ip_blacklist_path="/usr/share/wechatpush/api/ip_blacklist"
	oui_base="${dir}/oui_base.txt"
	debuglevel=$(echo "$debuglevel") && [ -z "$debuglevel" ] && logfile="/dev/null" || logfile="${dir}/wechatpush.log"
	login_port_forward_list=$(echo "$login_port_forward_list" | sed 's/ /\n/g') 2>/dev/null
	up_down_push_blacklist=$(echo "$up_down_push_blacklist" | sed 's/ /\n/g') 2>/dev/null
	up_down_push_whitelist=$(echo "$up_down_push_whitelist" | sed 's/ /\n/g') 2>/dev/null
	device_aliases_path="/usr/share/wechatpush/api/device_aliases.list"
	always_check_ip_list=$(echo "$always_check_ip_list" | sed 's/ /\n/g') 2>/dev/null
	unattended_device_aliases=$(echo "$unattended_device_aliases" | sed 's/ /\n/g') 2>/dev/null
	client_usage_whitelist=$(echo "$client_usage_whitelist" | sed 's/ /\n/g') 2>/dev/null
	login_ip_white_list=$(echo "$login_ip_white_list" | sed 's/ /\n/g') 2>/dev/null
	mark_mac_list="${mac_online_list} ${mac_offline_list}"
	mark_mac_list=$(echo "$mark_mac_list" | sed 's/ /\n/g' | sed 's/-/ /') 2>/dev/null
	ipv4_urllist=$(cat /usr/share/wechatpush/api/ipv4.list) 2>/dev/null
	ipv6_urllist=$(cat /usr/share/wechatpush/api/ipv6.list) 2>/dev/null
	[ -z "$get_ipv4_mode" ] && get_ipv4_mode=0
	[ -z "$get_ipv6_mode" ] && get_ipv6_mode=0
	[ -z "$sleeptime" ] && sleeptime="60"
	[ -z "$login_ip_black_timeout" ] && login_ip_black_timeout="86400"
	[ -z "$login_ip_white_timeout" ] && login_ip_white_timeout="600"
	[ "$iw_version" ] && wlan_interface=$(iw dev 2>/dev/null | grep Interface | awk '{print $2}') >/dev/null 2>&1
	[ -z "$server_port" ] && server_port="22"
	output_dir="${dir}/json_output"
	mkdir -p "$output_dir"
	if (echo "$lite_enable" | grep -q "content") || [ -z "$jsonpath" ]; then
		str_title_start="" && str_title_end="" && str_splitline="" && str_linefeed="" && str_tab=""
	else
		read_json "str_title_start" "$jsonpath"
		read_json "str_title_end" "$jsonpath"
		read_json "str_linefeed" "$jsonpath"
		read_json "str_splitline" "$jsonpath"
		read_json "str_space" "$jsonpath"
		read_json "str_tab" "$jsonpath"
		read_json "_api" "$jsonpath"
	fi
	wait_and_cat
	disturb_text=$_api
	deltemp
	cron
}

# 初始化
init() {
	# 检测程序开关
	enable_detection
	[ -f "$logfile" ] && local logrow=$(grep -c "" "$logfile") || local logrow="0"
	[ "$logrow" -ne 0 ] && echo "---------------------------------------------------------------------------------------" >>${logfile}
	log_change "【初始化】start running..."
	if [ -f "/usr/share/wechatpush/errlog" ]; then
		cat /usr/share/wechatpush/errlog >${logfile}
		log_change "【！！！】载入上次重启前日志"
	fi

	# 配置检查
	[ ! -f "/usr/sbin/wrtbwmon" ] && log_change "【info】未安装 wrtbwmon ，流量统计不可用"
	[ -z "$ip_version" ] && log_change "【！！！】无法获取依赖项 iputils-arping 版本号，请确认插件是否正常运行"
	[ -z "$cu_version" ] && log_change "【！！！】无法获取依赖项 curl 版本号，请确认插件是否正常运行"
	[ -n "$jsonpath" ] && [ -z "${sckey}${tg_token}${pushplus_token}${corpid}${wxpusher_apptoken}${wxpusher_uids}${wxpusher_topicIds}" -a "${jsonpath}" != "/usr/share/wechatpush/api/diy.json" ] && log_change "【！！！】请填写正确的 key" && return 1
	local interfacelist=$(getinterfacelist) && [ -z "$interfacelist" ] && log_change "【！！！】存在多个接口或配置错误，可能无法获取接口在线时间等信息，请确认插件是否正常运行"
	[ -n "$notification_temp" ] && [ -n "$temperature_threshold" ] && local cpu_temp=$(soc_temp) || local cpu_temp="null"
	[ -z "$cpu_temp" ] && log_change "【！！！】无法读取设备温度，请检查命令"
	[ -n "$notification_load" ] && [ -n "$cpu_load_threshold" ] && local cpu_fuzai=$(cat /proc/loadavg | awk '{print $1}') 2>/dev/null || local cpu_fuzai="null"
	[ -z "$cpu_fuzai" ] && log_change "【！！！】无法读取设备负载，请检查命令"

	# 检查 JSON 文件格式是否有效
	if ! jq empty "$devices_json" >/dev/null 2>&1 || ! jq -e 'has("address") and has("devices") and has("disks")' "$devices_json" >/dev/null 2>&1; then
		[ -f "$devices_json" ] && log_change "【！！！】设备列表文件格式错误，需重新初始化，原文件已保存至 ${devices_json}.err" && mv "$devices_json" "${devices_json}.err"
	fi
	# 如果设备列表文件为空，初始化
	[ ! -s "$devices_json" ] && echo '{"address": [], "devices": [], "disks": []}' >"$devices_json"

	# 文件下载
	/usr/libexec/wechatpush-call "down_oui"

	# 文件清理
	rm -f ${dir}/sheep_usage ${dir}/old_sheep_usage ${dir}/client_usage_aliases ${dir}/old_client_usage_aliases /usr/share/wechatpush/errlog >/dev/null 2>&1
	LockFile unlock

	# 防火墙初始化
	[ -n "$login_web_black" ] && [ "$login_web_black" -eq "1" ] && init_ip_black "ipv4"
	[ -n "$login_web_black" ] && [ "$login_web_black" -eq "1" ] && init_ip_black "ipv6"
	[ -n "$port_knocking_enable" ] && [ "$port_knocking_enable" -eq "1" ] && init_ip_white "ipv4"
	[ -n "$port_knocking_enable" ] && [ "$port_knocking_enable" -eq "1" ] && init_ip_white "ipv6"
	tmp_ip_list=$(echo "$login_ip_white_list" | grep -v "^$" | sort -u)
	while IFS= read -r tmp_ip; do
		[ -n "$tmp_ip" ] && add_ip_white "$tmp_ip" "0"
	done <<<"$tmp_ip_list"
	set_ip_black

	return 0
}

# 创建计划任务
cron() {
	del_cron() {
		crontab -l | grep -v "wechatpush" | crontab -
	}
	re_cron() {
		/etc/init.d/cron stop
		/etc/init.d/cron start
	}
	del_cron
	if [ -z "$enable" ]; then
		re_cron
		return
	fi

	# 重置流量
	if [ -n "$reset_regularly" ] && [ "$reset_regularly" -eq "1" ]; then
		(
			crontab -l 2>/dev/null
			echo "0 0 * * * rm ${dir}/usage.db >/dev/null 2>&1"
		) | crontab -
		(
			crontab -l 2>/dev/null
			echo "0 0 * * * rm ${dir}/usage6.db >/dev/null 2>&1"
		) | crontab -
	fi

	# 定时发送
	if [ -n "$crontab_regular_time" ]; then
		crontab_regular_time=$(echo "$crontab_regular_time" | sed 's/ /,/g')
		(
			crontab -l 2>/dev/null
			echo "0 $crontab_regular_time * * * /usr/share/wechatpush/wechatpush send &"
		) | crontab -
	# 间隔发送
	elif [ -n "$crontab_interval_time" ]; then
		(
			crontab -l 2>/dev/null
			echo "0 */$crontab_interval_time * * * /usr/share/wechatpush/wechatpush send &"
		) | crontab -
	fi

	re_cron
}

# ------------------------------------
# 主程序
main() {
	# 限制并发进程
	dir="/tmp/wechatpush" && mkdir -p ${dir}
	get_config "thread_num"
	[ -z "$thread_num" ] || [ "$thread_num" -eq "0" ] && thread_num=5
	[ "$1" ] && [ $1 == "t1" ] && thread_num=1
	max_thread_num="$thread_num"

	FIFO_PATH="${dir}/fifo.$$"
	mkfifo "$FIFO_PATH"
	exec 5<>"$FIFO_PATH"
	rm "$FIFO_PATH" >/dev/null 2>&1

	for i in $(seq 1 "$max_thread_num"); do
		echo >&5
	done
	unset i

	# 定义锁文件
	lock_file="${dir}/wechatpush.lock"
	touch "$lock_file"

	# 设置信号处理
	trap cleanup SIGINT SIGTERM EXIT

	# 初始化
	if [ "$1" ]; then
		[ $1 == "soc" ] && get_config "soc_code" "server_host" "server_port"
		[ $1 == "soc" ] && soc_temp && exit $?
		[ $1 == "getip" ] && {
			get_config "get_ipv4_mode" "ipv4_interface" "get_ipv6_mode" "ipv6_interface"
			ipv4_urllist=$(cat /usr/share/wechatpush/api/ipv4.list) 2>/dev/null
			ipv6_urllist=$(cat /usr/share/wechatpush/api/ipv6.list) 2>/dev/null
			output_dir="${dir}/json_output" && mkdir -p "$output_dir"
			ip_changes getip && exit $?
		}
		silent_run read_config
		[ $1 == "send" ] && send && exit $?
		[ $1 == "test" ] && send test && exit $?
	else
		silent_run read_config
	fi

	# 载入在线设备
	init
	[ $? -eq 1 ] && log_change "【！！！】读取设置出错，请检查设置项 " && exit
	log_change "【初始化】载入在线设备..."
	silent_run monitor_logins
	>"${dir}/send_enable.lock" && first && deltemp
	log_change "【初始化】初始化完成"

	# 循环
	while [ "$enable" -eq "1" ]; do
		deltemp
		usage update

		# 网络状态与 IP 变动
		if [ "$get_ipv4_mode" -ne "0" ] || [ "$get_ipv6_mode" -ne "0" ]; then
			check_connect
			ip_changes
		fi

		# 设备列表
		if [ ! -f "${dir}/send_enable.lock" ]; then
			[ -n "$title" ] && echo "$title" >"${dir}/title"
			[ -n "$content" ] && echo "$content" >"${dir}/content"
			first
			[ -f "${dir}/title" ] && title=$(cat "${dir}/title") && rm -f "${dir}/title" >/dev/null 2>&1
			[ -f "${dir}/content" ] && content=$(cat "${dir}/content") && rm -f "${dir}/content" >/dev/null 2>&1
		fi

		# 离线二次验证区推送
		[ ! -f "${dir}/send_enable.lock" ] && down_send

		# 当前设备列表
		[ -n "$content" ] && [ ! -f "${dir}/send_enable.lock" ] && current_device

		# 无人值守任务
		[ ! -f "${dir}/send_enable.lock" ] && unattended

		# CPU 检测
		[ ! -f "${dir}/send_enable.lock" ] && cpu_load

		# 硬盘检测
		#[ ! -f "${dir}/send_enable.lock" ] && get_disk

		# 异常流量检测
		[ ! -f "${dir}/send_enable.lock" ] && get_client_usage

		# 登录提醒通知
		#[ ! -f "${dir}/send_enable.lock" ] && login_send
		# 因修改为实时推送，白名单设备并不会再更新时间，暂时性修复
		# 修改为实时推送后，防火墙列表读取和设置需单独列出，移除黑名单无法实时，待改善
		set_ip_black

		tmp_ip_list=$(echo "$login_ip_white_list" | grep -v "^$" | sort -u)
		while IFS= read -r tmp_ip; do
			[ -n "$tmp_ip" ] && add_ip_white "$tmp_ip" "0"
		done <<<"$tmp_ip_list"

		# 推送
		if [ ! -f "${dir}/send_enable.lock" ] && [ -n "$title" ] && [ -n "$content" ]; then
			[ -n "$device_name" ] && title="【$device_name】$title"
			(echo "$lite_enable" | grep -q "content") && content="$title"
			disturb
			disturb_RETVAL=$?
			[ "$disturb_RETVAL" -eq 0 ] && diy_send "${title}" "${content}" "${jsonpath}" >/dev/null 2>&1
		fi

		sleep $sleeptime

		# 等待定时任务推送完成
		while [ -f "${dir}/send_pid" ]; do
			pid=$(cat "${dir}/send_pid")
			if ps | grep "^\s*${pid}\s" >/dev/null; then
				sleep 1
			else
				rm -f "${dir}/send_pid"
				break
			fi
		done
	done
}

# ------------------------------------
# 工具函数类
#
# 在调试模式下隐藏输出
# 不能直接包裹 var=$(echo $ssh_command) 等命令，待完善
silent_run() {
	"$@" >/dev/null 2>&1
}

# 处理并检查 tmp_name 的通用函数
process_and_check() {
	[ -z "$1" ] && return 1
	local value=$(echo "$1" | tr -d '\n\r' | awk '$1=$1' | sed 's/_/ /g' | grep -v "^$" | sort -u | head -n1)
	[ "$value" == "unknown" ] && value=""
	[ -n "$value" ] && echo "$value" && return 0
	return 1
}

# 获取文件最后修改时间（距离现在过去了多少秒）
file_date() {
	local file_dir="$1"
	local datetime=$(date -r "${file_dir}" +%s 2>/dev/null || echo "0")
	expr $(date +%s) - ${datetime}
}

# 流量数据单位换算
bytes_for_humans() {
	[ -z "$1" ] && return
	[ "$1" -gt 1073741824 ] && echo "$(awk 'BEGIN{printf "%.2f\n",'$1'/'1073741824'}') GB" && return
	[ "$1" -gt 1048576 ] && echo "$(awk 'BEGIN{printf "%.2f\n",'$1'/'1048576'}') MB" && return
	[ "$1" -gt 1024 ] && echo "$(awk 'BEGIN{printf "%.2f\n",'$1'/'1024'}') KB" && return
	echo "${1} bytes"
}

# 时间单位换算
time_for_humans() {
	[ -z "$1" ] && return

	if [ "$1" -lt 60 ]; then
		echo "${1} 秒"
	elif [ "$1" -lt 3600 ]; then
		local usetime_min=$(expr $1 / 60)
		local usetime_sec=$(expr $usetime_min \* 60)
		local usetime_sec=$(expr $1 - $usetime_sec)
		echo "${usetime_min} 分 ${usetime_sec} 秒"
	elif [ "$1" -lt 86400 ]; then
		local usetime_hour=$(expr $1 / 3600)
		local usetime_min=$(expr $usetime_hour \* 3600)
		local usetime_min=$(expr $1 - $usetime_min)
		local usetime_min=$(expr $usetime_min / 60)
		echo "${usetime_hour} 小时 ${usetime_min} 分"
	else
		local usetime_day=$(expr $1 / 86400)
		local usetime_hour=$(expr $usetime_day \* 86400)
		local usetime_hour=$(expr $1 - $usetime_hour)
		local usetime_hour=$(expr $usetime_hour / 3600)
		echo "${usetime_day} 天 ${usetime_hour} 小时"
	fi
}

# 计算字符串显示宽度
length_str() {
	[ -z "$1" ] && return

	local length_zh=$(echo "$1" | awk '{print gensub(/[\u4e00-\u9FA5A-Za-z0-9_]/,"","g",$0)}' | awk -F "" '{print NF}')
	local length_en=$(echo "$1" | awk '{print gensub(/[^\u4e00-\u9FA5A-Za-z0-9_]/,"","g",$0)}' | awk -F "" '{print NF}')

	echo $((length_zh / 3 * 2 + length_en))
}

# 字符串显示宽度处理
cut_str() {
	[ -z "$1" ] && return
	[ -z "$2" ] && return

	if [ $(length_str "$1") -le "$2" ]; then
		echo -n "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
		return
	fi

	local columns=$(echo "$1" | awk '{print NF}')
	if [ "$columns" -gt 1 ]; then
		local max_length=0
		local max_column=1
		for i in $(seq 1 $columns); do
			local length=$(echo "$1" | awk '{print gensub(/[\u4e00-\u9FA5A-Za-z0-9_]/,"","g",$0)}' | awk -F "" '{print NF}')
			if [ "$length" -gt "$max_length" ]; then
				max_length=$length
				max_column=$i
			fi
		done
		unset i

		local text=$(echo -n "$1" | cut -d ' ' -f $max_column)
		if [ $(length_str "$text") -le "$2" ]; then
			echo "$text" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
			return
		fi
	fi

	local temp_length=$2
	while [ $(length_str "$(echo -n "$1" | cut -c -$temp_length)") -lt "$2" ]; do
		temp_length=$(expr $temp_length + 1)
	done
	while [ $(printf "%d" \'$(echo -n "$1" | cut -c $temp_length)) -ge "128" ] && [ $(printf "%d" \'$(echo -n "$1" | cut -c $temp_length)) -lt "224" ]; do
		temp_length=$(expr "$temp_length" + 1)
	done
	temp_length=$(expr $temp_length - 1)

	echo "$(echo -n "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | cut -c -$temp_length).."
}

# 随机数
rand() {
	local min=$1
	local max=$(($2 - $min + 1))
	local num=$(date +%s%N)
	echo $(($num % $max + $min))
}

# 日志输出函数，待设置调试等级
log_change() {
	local message="$1"
	echo "$(date "+%Y-%m-%d %H:%M:%S")" "${message}" >>"$logfile"
}

# 重启网络服务
network_restart() {
	echo '#!/bin/sh' >"${dir}/network_restart"
	echo '/etc/init.d/network restart >/dev/null 2>&1 &' >>"${dir}/network_restart"
	echo '/etc/init.d/firewall restart >/dev/null 2>&1 &' >>"${dir}/network_restart"
	echo '/etc/init.d/dnsmasq restart >/dev/null 2>&1 &' >>"${dir}/network_restart"

	chmod 0755 "${dir}/network_restart"
	"${dir}/network_restart"
	rm -f "${dir}/network_restart" >/dev/null 2>&1
}

# 文件锁
LockFile() {
	local fd=200

	if [ "$1" = "lock" ]; then
		eval "exec $fd>$lock_file"
		flock -n $fd
		if [ $? -ne 0 ]; then
			while ! flock -n $fd; do
				sleep 1
			done
		fi
	elif [ "$1" = "unlock" ]; then
		eval "exec $fd>&-"
	fi
}

# 检测退出信号
cleanup() {
	LockFile unlock
	exit 0
}

# 清理临时文件
deltemp() {
	unset title content gateway_iplist
	rm -f "${dir}/title" "${dir}/content" "${dir}/send_enable.lock" "${tempjsonpath}" "${dir}/cookies.txt" >/dev/null 2>&1
	[ -f "$logfile" ] && local logrow=$(grep -c "" "$logfile") || local logrow="0"
	[ "$logrow" -gt 500 ] && tail -n 300 "$logfile" >"${logfile}.tmp" && mv "${logfile}.tmp" "$logfile" && log_change "【清理】日志超出上限，保留最后 300 条"
}

# ------------------------------------
# 信息获取类
#
# 获取 ip
getip() {
	[ -z "$1" ] && return

	# 从接口获取 IPv4
	if [ $1 == "wanipv4" ]; then
		[ -n "$ipv4_interface" ] && local wanIP=$(/sbin/ifconfig ${ipv4_interface} | awk '/inet addr/ {print $2}' | awk -F: '{print $2}' | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
		[ -z "$ipv4_interface" ] && local wanIP=$(getinterfacelist | grep '\"address\"' | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
		echo "$wanIP"
	# 从 URL 获取 IPv4
	elif [ $1 == "hostipv4" ]; then
		local sorted_ipv4_urllist=$(echo "$ipv4_urllist" | awk 'BEGIN {srand()} {print rand() "\t" $0}' | sort -k1,1n | cut -f2-)
		local ipv4_URL
		while IFS= read -r ipv4_URL; do
			[ -n "$ipv4_interface" ] && local tmp_hostIP=$(eval "curl --connect-timeout 2 -m 2 -k -s -4 --interface ${ipv4_interface} -m 5 ${ipv4_URL}") || local tmp_hostIP=$(eval "curl --connect-timeout 2 -m 2 -k -s -4 -m 5 ${ipv4_URL}")
			[ -z "$tmp_hostIP" ] && log_change "【info】IP 获取失败，当前使用的 API 为 $ipv4_URL & ${ipv4_interface}"
			local tmp_hostIP=$(echo $tmp_hostIP | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | head -n1)
			[ -n "$tmp_hostIP" ] && local tmp_hostIP="{\"IP\":\"${tmp_hostIP}\", \"URL\":\"${ipv4_URL}\"}" && break
		done <<<"$sorted_ipv4_urllist"
		echo $tmp_hostIP
	# 从接口获取 IPv6
	elif [ $1 == "wanipv6" ]; then
		[ -n "$ipv6_interface" ] && local wanIPv6=$(ip -6 addr show dev ${ipv6_interface} | grep "inet6" | grep "global dynamic noprefixroute" | awk '{print $2}' | grep -Ev "^(fc|fd)" | tail -n 1 | grep -oE "([\da-fA-F0-9]{1,4}(:{1,2})){1,15}[\da-fA-F0-9]{1,4}")
		[ -z "$ipv6_interface" ] && local wanIPv6=$(ip -6 addr show | grep "inet6" | grep "global dynamic noprefixroute" | awk '{print $2}' | grep -Ev "^(fc|fd)" | tail -n 1 | grep -oE "([\da-fA-F0-9]{1,4}(:{1,2})){1,15}[\da-fA-F0-9]{1,4}")
		echo "$wanIPv6"
	# 从 URL 获取 IPv6
	elif [ $1 == "hostipv6" ]; then
		local sorted_ipv6_urllist=$(echo "$ipv6_urllist" | awk 'BEGIN {srand()} {print rand() "\t" $0}' | sort -k1,1n | cut -f2-)
		local ipv6_URL
		while IFS= read -r ipv6_URL; do
			[ -n "$ipv6_interface" ] && local tmp_hostIPv6=$(eval "curl --connect-timeout 2 -m 2 -k -s -6 --interface ${ipv6_interface} -m 5 ${ipv6_URL}") || local tmp_hostIPv6=$(eval "curl --connect-timeout 2 -m 2 -k -s -6 -m 5 ${ipv6_URL}")
			[ -z "$tmp_hostIPv6" ] && log_change "【info】IP 获取失败，当前使用的 API 为 $ipv6_URL & ${ipv6_interface}"
			local tmp_hostIPv6=$(echo $tmp_hostIPv6 | grep -oE "([\da-fA-F0-9]{1,4}(:{1,2})){1,15}[\da-fA-F0-9]{1,4}" | head -n1)
			[ -n "$tmp_hostIPv6" ] && local tmp_hostIP="{\"IP\":\"${tmp_hostIPv6}\", \"URL\":\"${ipv6_URL}\"}" && break
		done <<<"$sorted_ipv6_urllist"
		echo $tmp_hostIP
	fi
}

# 查询设备接口
getinterface() {
	local ip=${1}
	local mac=${2}
	local interface

	[ -z "$mac" ] && return
	[ "$mac" == "unknown" ] && return

	# 从已保存的地址中获取接口
	interface=$(jq -r --arg ip "$ip" --arg mac "$mac" '.devices[] | select(.ip == $ip and .mac == $mac) | .interface' "$devices_json")
	process_and_check "$interface" && return

	# 如果定义了 WLAN 接口列表，则查询每个接口
	if [ -n "$wlan_interface" ]; then
		for interface_wlan in $wlan_interface; do
			interface=$(iw dev "$interface_wlan" station dump 2>/dev/null | grep -i -w "$mac" | sed -nr 's#^.*on (.*))#\1#gp')
			process_and_check "$interface" && return
		done
	fi

	# 查询 ARP 表获取接口
	interface=$(cat /proc/net/arp | grep "0x2\|0x6" | grep -i -w "$mac" | awk '{print $6}')
	process_and_check "$interface" && return
}

# 查询 MAC 地址
getmac() {
	local ip="$1"
	local mac
	[ -z "$ip" ] && return 1

	# 已保存的 MAC
	mac=$(jq -r --arg ip "$ip" '.devices[] | select(.ip == $ip) | .mac' "$devices_json")
	# 某些路由器中继模式，会导致 MAC 重复，如果是重复值，尝试重新获取
	local mac_count=$(jq -r --arg mac "$mac" '.devices[] | select(.mac == $mac) | .mac' "$devices_json" | wc -l)
	[ "$mac_count" -eq 1 ] && process_and_check "$mac" && return

	# DHCP
	[ -f "/tmp/dhcp.leases" ] && mac=$(grep -w "${ip}" "/tmp/dhcp.leases" | awk '{print $2}')
	process_and_check "$mac" && return

	# ARP
	mac=$(cat /proc/net/arp | grep "0x2\|0x6" | grep -w "${ip}" | awk '{print $4}')
	process_and_check "$mac" && return

	# 定时推送时不查询
	[ -f "${dir}/send_pid" ] && echo "unknown" && return

	# 离线、在线时间超过 10 分钟不查询
	local uptime=$(jq -r --arg ip "$ip" '.devices[] | select(.ip == $ip and .status == "online") | .uptime' "$devices_json")
	[ -n "$uptime" ] && local time_up=$(expr $(date +%s) - $uptime) && [ "$time_up" -lt 600 ] && mac=$(nmblookup_timeout "${ip}" "mac" "5" 2>/dev/null)
	process_and_check "$mac" && return

	# 默认返回 unknown
	echo "unknown"
}

# 查询主机名
getname() {
	local ip="$1"
	local mac="$2"
	local tmp_name
	local oui_name
	[ -z "$ip" ] && return 1
	[ -z "$mac" ] && return 1

	# 自定义备注
	tmp_name=$(cat "$device_aliases_path" 2>/dev/null | grep -i -Ew "^${ip}|^${mac}" | awk '{for(i=2; i<=NF; i++) printf $i " "; print ""}')
	process_and_check "$tmp_name" && return

	# MAC 获取失败时直接返回 unknown，优先级不能最高，自定义备注可以被 IP 匹配
	echo "$mac" | grep -q -w "unknown\|*" && echo "unknown" && return

	# 已保存的主机名
	tmp_name=$(jq -r --arg ip "$ip" --arg mac "$mac" '.devices[] | select(.ip == $ip and .mac == $mac) | .name' "$devices_json")
	[ -n "$oui_name" ] && [ -n "$tmp_name" ] && [ "$oui_name" == "$tmp_name" ] && tmp_name=""
	process_and_check "$tmp_name" && return

	# 静态地址备注名
	local dhcp_config=$(uci show dhcp | grep -i -w "${ip}\|${mac}" | sed -n 's/\(dhcp\..*\)\..*/\1/p')
	[ -n "$dhcp_config" ] && tmp_name=$(uci get ${dhcp_config}.name)
	process_and_check "$tmp_name" && return

	# DHCP
	[ -f "/tmp/dhcp.leases" ] && tmp_name=$(grep -w "${ip}" /tmp/dhcp.leases | awk '{print $4}')
	process_and_check "$tmp_name" && return

	# 定时推送时不查询
	[ -f "${dir}/send_pid" ] && echo "unknown" && return

	# 光猫
	[ -f "${dir}/gateway_info" ] && tmp_name=$(cut_str "$(grep -w "${ip}" "${dir}/gateway_info" | awk '{print $2}')" "30")
	process_and_check "$tmp_name" && return

	# 因 NetBIOS 查询时间过长，跳过离线设备
	# 设备刚上线时设备信息未保存，json 中无法检查在线状态，应避免使用 oui 数据库，否则无法使用 NetBIOS 查询
	# 离线、在线时间超过 10 分钟不查询
	local uptime=$(jq -r --arg ip "$ip" '.devices[] | select(.ip == $ip and .status == "online") | .uptime' "$devices_json")
	if [ -n "$uptime" ] && time_up=$(expr $(date +%s) - $uptime) && [ "$time_up" -lt 600 ]; then
		tmp_name=$(nmblookup_timeout "${ip}" "name" "5" 2>/dev/null)
		process_and_check "$tmp_name" && return

		# MAC 设备信息数据库
		[ -f "$oui_base" ] && oui_name=$(grep -i "$(echo "$mac" | cut -c 1,2,4,5,7,8)" "$oui_base" | sed -nr 's#^.*16)..(.*)#\1#gp')
		[ -n "$oui_data" ] && [ "$oui_data" -eq "4" ] && oui_name=$(curl -sS "https://standards-oui.ieee.org/oui/oui.txt" | grep -i "$(echo "$mac" | cut -c 1,2,4,5,7,8)" | sed -nr 's#^.*16)..(.*)#\1#gp')
		tmp_name="$oui_name"
		process_and_check "$tmp_name" && return
	fi

	echo "unknown"
}

# 获取接口信息
getinterfacelist() {
	[ $(ubus list | grep -w -i "network.interface.wan" | wc -l) -ge "1" ] && ubus call network.interface.wan status || ubus call network.interface.lan status && return
	[ -n "$ipv4_interface" ] && local device_name=$ipv4_interface || [ -n "$ipv6_interface" ] && local device_name=$ipv6_interface
	[ -n "$device_name" ] && local interface_name=$(ubus call network.interface dump | jq -r --arg intf "$device_name" '.interface[] | select(.device == $intf and (.interface | endswith("6") | not)) | .interface')
	[ -z "$interface_name" ] && local interface_name=$(ubus list | grep -i "network.interface." | grep -v "loopback" | grep -v -i "wan6" | grep -v -i "wan_6" | grep -v -i "lan6" | grep -v -i "ipsec.*" | grep -v -i "VPN.*" | grep -v -i "DOCKER.*" | awk -F '.' '{print $3}' | head -n1)
	ubus call network.interface.${interface_name} status && return
}

# 获取接口在线时间
getinterfaceuptime() {
	getinterfacelist | awk -F'\"uptime\": ' '/uptime/ { gsub(/,/, "", $2); print $2 }'
}

# 查询 IP 归属地
get_ip_attribution() {
	ip="$1"
	jq -e --arg ip "$ip" '.devices[] | select(.ip == $ip) | .ip' "$devices_json" >/dev/null && echo "本地局域网" && return
	local ip_attribution_urls=$(cat /usr/share/wechatpush/api/ip_attribution.list)
	local sorted_attribution_urls=$(echo "$ip_attribution_urls" | awk 'BEGIN {srand()} {print rand() "\t" $0}' | sort -k1,1n | cut -f2-)
	local ip_attribution_url
	while IFS= read -r ip_attribution_url; do
		local login_ip_attribution=$(eval curl --connect-timeout 2 -m 2 -k -s "$ip_attribution_url" 2>/dev/null)
		#logfile=logfile="${dir}/wechatpush.log" # 此处保留排查时使用
		#[ -z "$login_ip_attribution" ] && echo "`date "+%Y-%m-%d %H:%M:%S"` 【info】归属地获取超时，当前使用的 API 为 $ip_attribution_url" >> ${logfile}
		[ -n "$login_ip_attribution" ] && echo "$login_ip_attribution" && break
	done <<<"$sorted_attribution_urls"
}

# 查询 NetBIOS
nmblookup_timeout() {
	local ip="$1"
	local query_type="$2" # "name" or "mac"
	local max_wait_time="$3"
	local result=""
	[ -z "$ip" ] && return 1
	[ -z "$query_type" ] && return 1
	[ -z "$max_wait_time" ] && return 1

	{
		if [ "$query_type" == "name" ]; then
			result=$(nmblookup -A ${ip} 2>/dev/null | awk '/<00>/ && !/<GROUP>/ {print $1}')
		elif [ "$query_type" == "mac" ]; then
			result=$(nmblookup -A ${ip} 2>/dev/null | grep -oE 'MAC\s+Address\s+=\s+([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})' | awk '{print $NF}')
		fi
		echo "$result"
	} &
	local nmblookup_pid=$!

	# 等待最多 $max_wait_time 秒
	local wait_time=0
	while [ $wait_time -lt $max_wait_time ]; do
		# 检查子进程是否已完成
		if ! kill -0 $nmblookup_pid >/dev/null 2>&1; then
			result=$(wait $nmblookup_pid) # 获取子进程的输出
			break
		fi
		sleep 1
		wait_time=$((wait_time + 1))
	done

	if kill -0 $nmblookup_pid >/dev/null 2>&1; then
		kill $nmblookup_pid
	fi

	echo "$result"
}

# 查询 http/https 端口
check_http_access() {
	local ip="$1"
	[ -z "$ip" ] && return 1

	curl --head --silent --fail --connect-timeout 3 "http://$ip" >/dev/null
	[ $? -eq 0 ] && echo "http" && return

	curl --head --silent --fail --connect-timeout 3 "https://$ip" >/dev/null
	[ $? -eq 0 ] && echo "https" && return

	return 1
}

# ping
getping() {
	local ip="$1"
	local mac="$2"
	local timeout="$3"
	local retry_count="$4"

	local interface=$(getinterface "ip" "$mac")
	[ "$iw_version" ] && [ "$interface" ] && wlan_online=$(iw dev ${interface} station dump 2>/dev/null | grep -i -w "$mac" | grep Station) >/dev/null 2>&1
	[ "$wlan_online" ] && return 0

	interface=$(cat /proc/net/arp | grep -w "$ip" | awk '{print $6}' | grep -v "^$" | sort -u | head -n1)
	for i in $(seq 1 "$retry_count"); do
		# arping 应使用 br-lan 等接口，有 WiFi 的设备接口为 wlan*，重新获取
		[ -n "$interface" ] && ip_ms=$(arping -I ${interface} -c 20 -f -w "$timeout" "$ip") 2>/dev/null
		(! echo "$ip_ms" | grep -q "ms") && ip_ms=$(ping -c 5 -w "$timeout" "$ip" | grep -v '100% packet loss') 2>/dev/null
		(! echo "$ip_ms" | grep -q "ms") && sleep 1
	done
	unset i
	echo "$ip_ms" | grep -q "ms"
}

# CPU 占用率
getcpu() {
	local AT=$(cat /proc/stat | grep "^cpu " | awk '{print $2+$3+$4+$5+$6+$7+$8 " " $2+$3+$4+$7+$8}')
	sleep 1
	local BT=$(cat /proc/stat | grep "^cpu " | awk '{print $2+$3+$4+$5+$6+$7+$8 " " $2+$3+$4+$7+$8}')
	printf "%.01f%%" $(echo ${AT} ${BT} | awk '{print (($4-$2)/($3-$1))*100}')
}

# 获取SOC温度 （取所有传感器温度最大值）
soc_temp() {
	[ -n "$soc_code" ] && eval $(echo "$soc_code") 2>/dev/null && return 0
	getsensors() {
		# Intel
		local sensor_field1='["coretemp-isa-0000"]["Package id 0"]["temp1_input"]'
		# AMD
		local sensor_field2='["zenpower-pci-00c3"]["Tctl"]["temp1_input"]'
		local sensor_field3='["k10temp-pci-00c3"]["Tctl"]["temp1_input"]'
		eval "${1} sensors -j 2>/dev/null" | jq -r "
		if .${sensor_field1} != null then
		  .${sensor_field1}
		elif .${sensor_field2} != null then
		  .${sensor_field2}
		elif .${sensor_field3} != null then
		  .${sensor_field3}
		else
		  null
		end // \"\"
		"
	}

	[ -n "$server_host" ] && local ssh_command="ssh -o StrictHostKeyChecking=yes -o BatchMode=yes -i /root/.ssh/id_rsa root@${server_host} -p ${server_port}"
	local temperature=$(getsensors "$ssh_command" 2>/dev/null)
	# 通用（只能取最高温度，不一定是 CPU，特殊设备自行修改）
	# 将 grep °C 改为温度所在行的特别字符串，如 grep Core 0 等，就可以指定设备了
	[ -z "$temperature" ] && local temperature=$(sensors 2>/dev/null | grep °C | sed -nr 's#^.*:.*\+(.*)°C .*#\1#gp' | sort -nr | head -n1)
	# 将 thermal_zone* 改为 thermal_zone0 thermal_zone1 等，就可以指定设备了
	[ -z "$temperature" ] && local temperature=$(cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | sort -nr | head -n1 | cut -c-2)
	printf "%.1f" "$temperature"
}

# 流量数据
usage() {
	[ ! -f "/usr/sbin/wrtbwmon" ] || [ -z "$1" ] && return
	# 更新
	if [ $1 == "update" ]; then
		version_le() { test "$(echo "$@" | tr " " "\n" | sort -n | head -n 1)" == "$1"; }
		version_ge() { test "$(echo "$@" | tr " " "\n" | sort -r | head -n 1)" == "$1"; }
		[ -n "$wr_version" ] && (version_ge "${wr_version}" "1.2.0") && wrtbwmon -f ${dir}/usage.db 2>/dev/null && return
		[ -n "$wr_version" ] && (version_le "${wr_version}" "1.0.0") || [ -z "$wr_version" ] && wrtbwmon update ${dir}/usage.db 2>/dev/null && return
	# 获取
	elif [ $1 == "get" ]; then
		[ ! -f "${dir}/usage.db" ] && [ -z "$3" ] && echo $(bytes_for_humans "0") && return
		[ ! -f "${dir}/usage.db" ] && [ -n "$3" ] && echo "0" && return
		[ -z "$total_n" ] && total_n=$(cat ${dir}/usage.db | head -n1 | grep "total" | sed 's/,/\n/g' | awk '/total/{print NR}') 2>/dev/null
		[ -z "$total_n" ] && total_n="6"
		[ -n "$2" ] && local tmptotal=$(cat ${dir}/usage.db | sed 's/,,,/,0,0,/g' | sed 's/,,/,0,/g' | sed 's/,/ /g' | grep -i -w ${2} | awk "{print "'$'$total_n"}" | grep -v "^$" | sort -u | head -n1) 2>/dev/null
		[ -z "$tmptotal" ] && local tmptotal="0"
		[ -z "$3" ] && echo $(bytes_for_humans "${tmptotal}") || echo "$tmptotal"
	# 剔除
	elif [ $1 == "down" ]; then
		[ "$2" ] && sed -i "/,${2},/d" ${dir}/usage.db 2>/dev/null
	fi
}

# ------------------------------------
# 需要经常调用的偷懒类
#
# 检测程序开关
enable_detection() {
	[ -z "$1" ] && local time_n=1
	for i in $(seq 1 $time_n); do
		get_config enable
		[ -z "$enable" ] || [ "$enable" -eq "0" ] && exit || sleep 1
	done
	unset i
}

# 免打扰检测
disturb() {
	[ -z "$do_not_disturb_mode" ] || [ -z "$do_not_disturb_starttime" ] || [ -z "$do_not_disturb_endtime" ] && return 0

	# 非免打扰时间
	if [ $(date +%H) -ge $do_not_disturb_endtime -a $do_not_disturb_starttime -lt $do_not_disturb_endtime ] || [ $(date +%H) -lt $do_not_disturb_starttime -a $do_not_disturb_starttime -lt $do_not_disturb_endtime ] || [ $(date +%H) -lt $do_not_disturb_starttime -a $(date +%H) -ge $do_not_disturb_endtime -a $do_not_disturb_starttime -gt $do_not_disturb_endtime ]; then
		unset sheep_starttime
		rm -f ${dir}/sheep_usage ${dir}/old_sheep_usage 2>/dev/null
		[ -z "$jsonpath" ] && disturb_text="【info】"
		[ -n "$jsonpath" ] && disturb_text=$(jq -r '._api' ${jsonpath})
		return 0
	# 免打扰
	else
		[ -z "$sheep_starttime" ] && log_change "【免打扰】夜深了，该休息了" && sheep_starttime=$(date +%s)
		# 挂起
		if [ "$do_not_disturb_mode" -eq "1" ]; then
			while [ $(date +%H) -lt "$do_not_disturb_endtime" ]; do
				enable_detection
				sleep $sleeptime
			done
		# 静默
		elif [ "$do_not_disturb_mode" -eq "2" ]; then
			disturb_text="【免打扰】"
			return 1
		fi
	fi
}

# 检测黑白名单
blackwhitelist() {
	local mac="$1"
	[ -z "$mac" ] && return 1

	# return 1 免打扰
	# return 0 正常推送

	# 没有打开免打扰功能
	[ -z "$up_down_push_whitelist" ] && [ -z "$up_down_push_blacklist" ] && [ -z "$up_down_push_interface" ] && [ -z "$mac_online_list" ] && [ -z "$mac_offline_list" ] && return 0

	# 忽略列表内设备
	[ -n "$up_down_push_whitelist" ] && (echo "$up_down_push_whitelist" | grep -q -i -w "$mac") && return 1
	# 仅通知列表内设备
	[ -n "$up_down_push_blacklist" ] && (! echo "$up_down_push_blacklist" | grep -q -i -w "$mac") && return 1
	# 仅通知接口
	[ -n "$up_down_push_interface" ] && (! echo $(getinterface "" "$mac") | grep -q -i -w $up_down_push_interface) && return 1

	for check_mac in $mark_mac_list; do
		# 设备在线时免打扰
		[ -n "$mac_online_list" ] && jq -e --arg mac "$check_mac" '.devices[] | select(.mac == $mac and .status == "online") | .mac' "$devices_json" >/dev/null && return 1
		# 设备离线时免打扰
		[ -n "$mac_offline_list" ] && jq -e --arg mac "$check_mac" '.devices[] | select(.mac == $mac and .status == "offline") | .mac' "$devices_json" >/dev/null && return 1
	done

	unset check_mac
	return 0
}

# 查看无人值守任务设备是否在线
geterrdevicealiases() {
	[ -z "$unattended_device_aliases" ] && return
	local logrow=$(jq '.devices | map(select(.status == "online")) | length' "$devices_json")
	[ "$logrow" -eq 0 ] && return

	for mac in $unattended_device_aliases; do
		[ -n "$mac" ] && local unattended_mac=$(jq -r --arg mac "$mac" '.devices[] | select(.mac == $mac and .status == "online") | .mac' "$devices_json") && break
	done

	# 进入免打扰时间已经超过一小时
	if [ -n "$sheep_starttime" ] && [ "$(($(date +%s) - $sheep_starttime))" -ge "3600" ]; then
		>${dir}/sheep_usage
		local MACLIST=$(echo "$unattended_device_aliases" | grep -v "^$" | sort -u)
		while IFS= read -r mac; do
			[ -n "$mac" ] && local tmptotal=$(usage get ${mac} bytes)
			[ -n "$tmptotal" ] && awk 'BEGIN{printf "%.0f\n",'$tmptotal'/'204800'}' 2>/dev/null >>${dir}/sheep_usage
		done <<<"$MACLIST"
		[ -f ${dir}/old_sheep_usage ] && local old_sheep_usage=$(cat ${dir}/old_sheep_usage) 2>/dev/null || local old_sheep_usage=""
		[ -f ${dir}/sheep_usage ] && local sheep_usage=$(cat ${dir}/sheep_usage) 2>/dev/null || local sheep_usage=""
		[ "$old_sheep_usage" == "$sheep_usage" ] && [ -z "$sheep_nousage_starttime" ] && sheep_nousage_starttime=$(date +%s)
		[ "$old_sheep_usage" != "$sheep_usage" ] && unset sheep_nousage_starttime && cat ${dir}/sheep_usage 2>/dev/null >${dir}/old_sheep_usage
		[ -n "$sheep_nousage_starttime" ] && [ "$(($(date +%s) - $sheep_nousage_starttime))" -ge "300" ] && unset unattended_mac
	fi
	[ -z "$unattended_mac" ]
}

# ------------------------------------
# 网络状态相关
#
# 检测 ip 状况
ip_changes() {
	local IPv4_URL="网络接口"
	local IPv6_URL="网络接口"
	[ "$get_ipv4_mode" -eq "1" ] && getip wanipv4 >"$output_dir/IPv4" &
	[ "$get_ipv6_mode" -eq "1" ] && getip wanipv6 >"$output_dir/IPv6" &
	[ "$get_ipv4_mode" -eq "2" ] && local IPv4=$(getip hostipv4) && local IPv4_URL=$(echo ${IPv4} | jq -r '.URL') && local IPv4=$(echo ${IPv4} | jq -r '.IP')
	[ "$get_ipv6_mode" -eq "2" ] && local IPv6=$(getip hostipv6) && local IPv6_URL=$(echo ${IPv6} | jq -r '.URL') && local IPv6=$(echo ${IPv6} | jq -r '.IP')
	wait_and_cat

	if [ "$1" ] && [ "$1" == "getip" ]; then
		echo "IPv4：$IPv4<br/>地址：$(get_ip_attribution $IPv4)<br/>接口：$IPv4_URL<br/>IPv6：$IPv6<br/>地址：$(get_ip_attribution $IPv6)<br/>接口：$IPv6_URL"
		return
	fi
	local last_IPv4=$(jq -r '.address[0].IPv4 // empty' "$devices_json")
	local last_IPv6=$(jq -r '.address[0].IPv6 // empty' "$devices_json")
	silent_run LockFile lock

	if [ -z "$last_IPv4" ] && [ -z "$last_IPv6" ]; then
		log_change "${disturb_text}路由器已经重启!"
		title="路由器重新启动"
		content="${content}${str_splitline}${str_title_start} 路由器重新启动${str_title_end}"
	fi
	if [ "$get_ipv4_mode" -ne "0" ] && [ -n "$IPv4" ] && [ "$IPv4" != "$last_IPv4" ]; then
		log_change "${disturb_text}当前 IP：${IPv4} from：${IPv4_URL}"
		[ -z "$title" ] && title="IP 地址变化"
		[ -z "$content" ] && content="${content}${str_splitline}${str_title_start} IP 地址变化${str_title_end}"
		content="${content}${str_linefeed}${str_tab}当前 IP：  ${IPv4}"
		jq --arg IPv4 "$IPv4" '.address[0].IPv4 = $IPv4' "$devices_json" >"$tmp_devices_json" && mv "$tmp_devices_json" "$devices_json"

	fi
	if [ "$get_ipv6_mode" -ne "0" ] && [ -n "$IPv6" ] && [ "$IPv6" != "$last_IPv6" ]; then
		log_change "${disturb_text}当前 IPv6：${IPv6} from：${IPv6_URL}"
		[ -z "$title" ] && title="IPv6 地址变化"
		[ -z "$content" ] && content="${content}${str_splitline}${str_title_start} IPv6 地址变化${str_title_end}"
		content="${content}${str_linefeed}${str_tab}当前 IPv6：${IPv6}"
		jq --arg IPv6 "$IPv6" '.address[0].IPv6 = $IPv6' "$devices_json" >"$tmp_devices_json" && mv "$tmp_devices_json" "$devices_json"
	fi

	# IP 变化，悄咪咪的重启 zerotier
	if [ -n "$content" ] && [ -n "$zerotier_helper" ] && [ "$zerotier_helper" -eq "1" ]; then
		[ -z "$zerotier_enabled" ] && zerotier_enabled=$(uci get zerotier.sample_config.enabled)
		if [ -n "$zerotier_enabled" ] && [ $zerotier_enabled -eq "1" ]; then
			/etc/init.d/zerotier restart >/dev/null 2>&1
		fi
	fi
	silent_run LockFile unlock
}

# 检测网络状态
check_connect() {
	# 获取网络状态
	is_online() {
		local urls=(http://connect.rom.miui.com/generate_204 http://wifi.vivo.com.cn/generate_204 http://connectivitycheck.platform.hicloud.com/generate_204 http://www.apple.com/library/test/success.html)
		local shuffled_urls=($(for i in "${urls[@]}"; do echo "$i"; done | awk 'BEGIN {srand()} {print rand(), $0}' | sort -n | cut -d' ' -f2-))
		for url in "${shuffled_urls[@]}"; do
			local status_code
			status_code=$(curl -o /dev/null -sI -w "%{http_code}" "$url")
			[[ "$status_code" -eq 204 || "$status_code" -eq 200 ]] && return 0
		done
		unset i url
		return 1
	}

	local network_state="unknown"
	while true; do
		if is_online; then
			[ "$network_state" == "down" ] && log_change "【网络状态】网络恢复正常.."
			break
		else
			[ "$network_state" == "unknown" ] && log_change "【！！！】当前网络不通！停止检测！ "
			local network_state="down"
			# 无人值守、待弃用或改进
			[ -z "$network_unattended_time" ] && network_unattended_time=$(date +%s)
			if [ -n "$network_disconnect_event" ] && [ "$(($(date +%s) - $network_unattended_time))" -ge "300" ]; then
				>"${dir}/send_enable.lock" && first && deltemp
				geterrdevicealiases
				if [ "$?" -eq "0" ]; then
					[ -f /usr/share/wechatpush/autoreboot_count ] && retry_count=$(cat /usr/share/wechatpush/autoreboot_count) && rm -f /usr/share/wechatpush/autoreboot_count >/dev/null 2>&1
					[ -z "${retry_count}" ] && retry_count=0
					retry_count=$((retry_count + 1))
					if [ "$network_disconnect_event" -eq "1" ]; then
						if [ "$retry_count" -lt "3" ]; then
							echo "$retry_count" >/usr/share/wechatpush/autoreboot_count
							log_change "【！！！】正在尝试重启路由，当前第 $retry_count 次 "
							cat ${logfile} >/usr/share/wechatpush/errlog
							getgateway "reboot"
							sleep 2 && reboot && exit
						fi
						[ "$retry_count" -eq "3" ] && log_change "【！！！】已经重启路由2次，修复失败，请主人自行修复哦"
					elif [ "$network_disconnect_event" -eq "2" ]; then
						[ "$retry_count" -lt "3" ] && log_change "【！！！！】正在尝试重启网络，当前第 $retry_count 次 " && network_restart
						[ "$retry_count" -eq "3" ] && log_change "【！！！】已经重启网络2次，修复失败，请主人自行修复哦 "
					fi
				fi
			elif [ -f /usr/share/wechatpush/autoreboot_count ]; then
				network_unattended_time=$((network_unattended_time - 300)) && sleep 60
			fi
			enable_detection
			sleep $sleeptime
		fi
	done
	rm -f /usr/share/wechatpush/autoreboot_count >/dev/null 2>&1
}

# ------------------------------------
# 运行状态相关
#
# 检测 cpu 状态
cpu_load() {
	[ -z "$cpu_notification_duration" ] && cpu_notification_duration=$(time_for_humans $cpu_notification_delay)
	if [ -n "$notification_temp" ] && [ -n "$temperature_threshold" ]; then
		[ -z "$temp_last_overload_time" ] && temp_last_overload_time=$(date +%s)
		local cpu_temp=$(soc_temp)

		if [ -n "$cpu_temp" ] && [ $(expr $cpu_temp \> $temperature_threshold) -eq "1" ]; then
			log_change "【！！警报！！】 CPU 温度过高: ${cpu_temp}"
		else
			temp_last_overload_time=$(date +%s)
		fi

		if [ -n "$cpu_temp" ] && [ "$(($(date +%s) - $temp_last_overload_time))" -ge "$cpu_threshold_duration" ] && [ -z "$temperaturecd_time" ]; then
			title="CPU 温度过高！"
			temperaturecd_time=$(date +%s)
			log_change "${disturb_text} CPU 温度过高: ${cpu_temp}"

			local cpu_overload_duration=$(time_for_humans $(($(date +%s) - $cpu_last_overload_time)))

			local cpu_overload_info="${str_splitline}${str_title_start} CPU 温度过高${str_title_end}"
			local cpu_overload_info="${cpu_overload_info}${str_linefeed}${str_tab}CPU 温度已连续 ${cpu_overload_duration} 超过预设"
			local cpu_overload_info="${cpu_overload_info}${str_linefeed}${str_tab}接下来 ${cpu_notification_duration} 不再提示"
			local cpu_overload_info="${cpu_overload_info}${str_linefeed}${str_tab}当前温度：${cpu_temp}℃"

			content="${content}${cpu_overload_info}"
		elif [ -n "$temperaturecd_time" ] && [ "$(($(date +%s) - $temperaturecd_time))" -ge "$cpu_notification_delay" ]; then
			unset temperaturecd_time
		fi
	fi

	if [ -n "$notification_load" ] && [ -n "$cpu_load_threshold" ]; then
		[ -z "$cpu_last_overload_time" ] && cpu_last_overload_time=$(date +%s)
		local cpu_fuzai=$(cat /proc/loadavg | awk '{print $1}') 2>/dev/null

		if [ -n "$cpu_fuzai" ] && [ $(expr $cpu_fuzai \> $cpu_load_threshold) -eq "1" ]; then
			log_change "【！！警报！！】 CPU 负载过高: ${cpu_fuzai}"
			cputop log
		elif [ -n "$cpu_fuzai" ]; then
			cpu_last_overload_time=$(date +%s)
		fi

		if [ -n "$cpu_fuzai" ] && [ "$(($(date +%s) - $cpu_last_overload_time))" -ge "$cpu_threshold_duration" ] && [ -z "$cpucd_time" ]; then
			unset getlogtop

			if [ -n "$title" ] && (echo "$title" | grep -q "过高"); then
				title="设备报警！"
			else
				title="CPU 负载过高！"
			fi

			cpucd_time=$(date +%s)
			log_change "${disturb_text} CPU 负载过高: ${cpu_fuzai}"

			local cpu_load_duration=$(time_for_humans $(($(date +%s) - $cpu_last_overload_time)))

			local cpu_load_info="${str_splitline}${str_title_start} CPU 负载过高${str_title_end}"
			local cpu_load_info="${cpu_load_info}${str_linefeed}${str_tab}CPU 负载已连续 ${cpu_load_duration} 超过预设"
			local cpu_load_info="${cpu_load_info}${str_linefeed}${str_tab}接下来 ${cpu_notification_duration} 不再提示"
			local cpu_load_info="${cpu_load_info}${str_linefeed}${str_tab}当前负载：${cpu_fuzai}"

			content="${content}${cpu_load_info}"

			cputop
		elif [ -n "$cpucd_time" ] && [ "$(($(date +%s) - $cpucd_time))" -ge "$cpu_notification_delay" ]; then
			unset cpucd_time
		fi
	fi
}

# CPU 占用前三
cputop() {
	[ -z "$1" ] && content="${content}${str_splitline}${str_title_start} 当前 CPU 占用前三的进程${str_title_end}"
	local gettop=$(top -bn 1 | grep -v "top -bn 1" | head -n 7)
	for i in $(seq 5 7); do
		local top_name=$(echo "${gettop}" | awk 'NR=='${i} | awk '{print ($8 ~ /\/bin\/sh|\/bin\/bash/) ? $9 : $8}')
		local top_load=$(echo "${gettop}" | awk 'NR=='${i} | awk '{print $7}')
		local temp_top="${top_name} ${top_load}"
		[ -n "$1" ] && local logtop="$logtop  $temp_top"
		[ -z "$1" ] && content="${content}${str_linefeed}${str_tab}${temp_top}"
	done
	unset i
	[ -n "$1" ] && log_change "【！！警报！！】 CPU 占用前三: ${logtop}"
}

# 检测硬盘状态
get_disk() {
	mkdir -p "${dir}/disk_info" && >"$output_dir/get_disk"

	# 获取磁盘名称
	get_disk_names() {
		local disk_names=($(lsblk -dno NAME,TYPE 2>/dev/null | awk '$2=="disk" && $1 !~ /^mtd/ && $1 !~ /^ubiblock/ {print $1}' | sort -u))
		[ -z "$disk_names" ] && disk_names=($(df -h 2>/dev/null | awk '$1 ~ /^\/dev/ && !/^\/dev\/(mtd|ubiblock)/ {if ($NF ~ /^\/[a-zA-Z0-9]/) { sub("/dev/", "", $1); sub(/[0-9]+$/, "", $1); print $1 }}' | sort -u))
		echo "${disk_names[@]}"
	}

	# 查询本地硬盘名
	local local_disk_names=($(get_disk_names))

	# 查询远程硬盘名
	if [ -n "$server_host" ]; then
		local ssh_command="ssh -o StrictHostKeyChecking=yes -o BatchMode=yes -i /root/.ssh/id_rsa root@${server_host} -p ${server_port}"
		local remote_disk_names=$($ssh_command "$(declare -f get_disk_names); get_disk_names" | tr -d '\r')
		local remote_disk_names=($remote_disk_names)
		local remote_disk_tags=($(for _ in "${remote_disk_names[@]}"; do echo "remote"; done))
		# 删除重复的直通硬盘
		local get_host_disk_uuids=$(eval ${ssh_command} ls -l /dev/disk/by-uuid/ | awk '{print $9}')
		for uuid in $get_host_disk_uuids; do
			local fstab_config=$(uci show fstab | grep -i -w "${uuid}" | sed -n 's/\(fstab\..*\)\..*/\1/p')
			[ -n "$fstab_config" ] && local tmp_target=$(uci get ${fstab_config}.target)
			[ -n "$tmp_target" ] && local local_dev=$(df -h 2>/dev/null | grep -w "${tmp_target}" | awk '$1 ~ /^\/dev/ && !/^\/dev\/(mtd|ubiblock)/ {if ($NF ~ /^\/[a-zA-Z0-9]/) { sub("/dev/", "", $1); sub(/[0-9]+$/, "", $1); print $1 }}' | sort -u)
			[ -n "$local_dev" ] && local local_disk_names=(${local_disk_names[@]/$local_dev/})
		done
		unset uuid
	fi

	# 先删除重复的直通硬盘，再添加标记
	local local_disk_tags=($(for _ in "${local_disk_names[@]}"; do echo "local"; done))

	# 合并本地和远程硬盘名及标记
	local all_disk_names=("${local_disk_names[@]}" "${remote_disk_names[@]}")
	local all_disk_tags=("${local_disk_tags[@]}" "${remote_disk_tags[@]}")

	for i in "${!all_disk_names[@]}"; do
		local tmp_name=${all_disk_names[i]}
		local tmp_tag=${all_disk_tags[i]}
		local error_pattern="No such device|Unable to detect device type|Unknown USB bridge|QEMU HARDDISK"

		# 判断硬盘类型
		if [ "$tmp_tag" == "remote" ]; then
			local disk_type="_remote"
			local tmp_command="$ssh_command"
		fi
		local file_path="${dir}/disk_info/${tmp_name}${disk_type}"

		# 如果不能获取值，使用分区名重试（因为不清楚是 OpenWrt 的原因还是 smartctl 版本的原因，使用出错重试的方式） // 2024/07/10 - 未重新验证
		eval ${tmp_command} smartctl -i -n standby "/dev/${tmp_name}" 2>/dev/null | grep -qE "$error_pattern" && {
			local tmp_name=$(eval ${tmp_command} df -h | awk "/^\\/dev\\/${tmp_name}/ {print \$1}" | awk -F '/' '{print $NF}' | head -n1)
			[ -z "$tmp_name" ] && continue
		}

		# 手上的硬盘不能休眠，不确定命令是否会唤醒硬盘，每天只运行一次
		last_disk_time=$(date -r "${file_path}" +%s 2>/dev/null) || last_disk_time=0

		if [ $(($(date +%s) - $last_disk_time)) -gt 86000 ]; then
			local disk_info=$(eval ${tmp_command} smartctl -i -n standby "/dev/${tmp_name}" 2>/dev/null)
			[ -z "$disk_info" ] && {
				cat /sys/block/"${tmp_name}"/device/model 2>/dev/null | grep -qE "$error_pattern" && continue
			} ||
				echo "$disk_info" | grep -qE "$error_pattern" && {
				continue
			} ||
				echo "$disk_info" | grep -q "STANDBY" && {
				echo "$disk_info" >"${file_path}"
			} ||
				eval ${tmp_command} smartctl -a -j /dev/${tmp_name} 2>/dev/null >${file_path}
		fi

		# 硬盘状态
		if [ -f "${file_path}" ] && [ -s "${file_path}" ] && (! cat "${file_path}" | grep -q -v "STANDBY"); then
			local disk_name=$(awk '/Device Model/{print $NF}' "$file_path")
			[[ -n $disk_name && $disk_name != null && $disk_name != 0 ]] && {
				local disk_name=$(cut_str "$disk_name" "20")
				local disk_name="${disk_name}_$(eval ${tmp_command} lsblk -o NAME,SIZE | awk "/^${tmp_name}/ {print \$NF}")"
			}
			printf "${str_linefeed}${str_title_start} 硬盘名称：${disk_name}${disk_type}${str_title_end}${str_linefeed}${str_tab}硬盘休眠中" >>"$output_dir/get_disk"
		elif [ -f "${file_path}" ]; then
			# 硬盘名称
			local disk_name=$(jq -r .model_name ${file_path})
			[ -z "$disk_name" ] && local disk_name=$(cat /sys/block/"${tmp_name}"/device/model)
			[[ -n $disk_name && $disk_name != null && $disk_name != 0 ]] && {
				local disk_name=$(cut_str "$disk_name" "20")
				local disk_size=$(eval ${tmp_command} lsblk -o NAME,SIZE 2>/dev/null | awk "/^${tmp_name}/ {print \$NF}")
				[ -z "$disk_size" ] && disk_size=$(eval ${tmp_command} df -h 2>/dev/null | awk -v tmp_disk_name="${tmp_name}" '$1 ~ "^/dev/"tmp_disk_name && !disk_found {print $2; disk_found=1}')
				local disk_name="${disk_name}_${disk_size}"
			}
			printf "${str_linefeed}${str_title_start} 硬盘名称：${disk_name}${disk_type}${str_title_end}" >>"$output_dir/get_disk"
			# 硬盘温度
			local disk_temp=$(jq -r .temperature.current ${file_path})
			[[ -n $disk_temp && $disk_temp != null && $disk_temp != 0 ]] && printf "${str_linefeed}${str_tab}硬盘温度：${disk_temp}℃" >>"$output_dir/get_disk"
			# 通电时间
			local disk_time=$(jq -r .power_on_time.hours ${file_path})
			[[ -n $disk_time && $disk_time != null ]] && printf "${str_linefeed}${str_tab}通电时间：${disk_time}h" >>"$output_dir/get_disk"
			# 空间使用
			local disk_use=$(eval ${tmp_command} lsblk -no NAME,FSUSE% "/dev/${tmp_name}" 2>/dev/null | awk '$2 != "" { gsub(/^[^a-zA-Z0-9]+/, "", $1); printf "%s: %s\n", $1, $2 }')
			[ -z "$disk_use" ] && disk_use=$(eval ${tmp_command} df -h 2>/dev/null | awk -v part_name="${tmp_name}" '$1 ~ "^/dev/"part_name && NF > 1 {sub("/dev/", "", $1); if (!seen[$1]) { printf "%s: %s  ", $1, $5; seen[$1] = 1; }} END {print ""}')
			[ -z "$disk_use" ] && [ -n "$tmp_command" ] && {
				local uuid=$($tmp_command ls -l /dev/disk/by-uuid/ 2>/dev/null | grep "$disk" | awk '{print $9}')
				local fstab_config=$(uci show fstab 2>/dev/null | grep -i -w "${uuid}" | sed -n 's/\(fstab\..*\)\..*/\1/p')
				[ -n "$fstab_config" ] && local mount_point=$(uci get ${fstab_config}.target 2>/dev/null)
				[ -n "$mount_point" ] && local disk_use=$(df -h 2>/dev/null | awk -v mount="$mount_point" '$NF == mount {sub("^/dev/", "", $1); printf "%s: %s\n", $1, $5}')
			}
			# 如果获取到了多个分区，屏蔽使用空间为 0% 的分区
			[ -n "$disk_use" ] && [ $(echo "$disk_use" | wc -l) -ne "1" ] && disk_use=$(echo "$disk_use" | grep -v -w "0%")
			[ -n "$disk_use" ] && disk_use=$(echo "$disk_use" | sed ':a;N;$!ba;s/\n/'"${str_linefeed}${str_tab}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}"'/g')
			[ -n "$disk_use" ] && [ -n "${disk_use// /}" ] && echo -e -n "${str_linefeed}${str_tab}空间使用：${disk_use}" >>"$output_dir/get_disk"
			# 寿命使用
			local disk_health=$(jq -r .nvme_smart_health_information_log.percentage_used ${file_path})
			[[ -n $disk_health && $disk_health != null ]] && echo -e -n "${str_linefeed}${str_tab}寿命使用：${disk_health}%" >>"$output_dir/get_disk"
			# 错误日志
			local disk_log_err=$(jq -r .ata_smart_error_log.summary.count ${file_path})
			[[ -n $disk_log_err && $disk_log_err != null && $disk_log_err != 0 ]] && local disk_err="true" && printf "${str_linefeed}${str_tab}错误日志：${disk_log_err}" >>"$output_dir/get_disk"
			# 自检错误
			local disk_test_err=$(jq -r .ata_smart_self_test_log.standard.error_count_total ${file_path})
			[[ -n $disk_test_err && $disk_test_err != null && $disk_test_err != 0 ]] && local disk_err="true" && printf "${str_linefeed}${str_tab}自检错误：${disk_test_err}" >>"$output_dir/get_disk"
			# 0E 错误
			local disk_0e_err=$(jq -r .nvme_smart_health_information_log.media_errors ${file_path})
			[[ -n $disk_0e_err && $disk_0e_err != null && $disk_0e_err != 0 ]] && local disk_err="true" && printf "${str_linefeed}${str_tab}0E 错误：${disk_0e_err}" >>"$output_dir/get_disk"
			# 整体健康
			local smart_status=$(jq -r .smart_status.passed ${file_path})
			[[ -n $smart_status && $smart_status != null && $smart_status != "true" ]] && {
				echo -e -n "${str_linefeed}${str_tab}${str_title_start}硬盘整体健康评估不通过！！！${str_title_end}" >>"$output_dir/get_disk"
				local disk_err="true"
			}
			[ -n "$disk_err" ] && echo -e -n "${str_linefeed}${str_tab}${str_title_start}硬盘存在错误，请及时备份数据！！！${str_title_end}" >>"$output_dir/get_disk"
		fi
	done
	unset i
}

# ------------------------------------
# 设备在线状态相关
#
# 扫描范围内 IP
scanlocalip() {
	[ -z "$scan_ip_range" ] && return
	[ -z "$last_scan_ip_time" ] && last_scan_ip_time=0

	local current_time=$(date +%s)
	local elapsed_time=$((current_time - last_scan_ip_time))

	# 判断是否需要重新扫描
	if [ "$elapsed_time" -ge "$device_info_helper_sleeptime" ]; then
		local start_ip=$(echo "$scan_ip_range" | cut -d "-" -f 1)
		local end_ip=$(echo "$scan_ip_range" | cut -d "-" -f 2)

		local start_num=$(echo "$start_ip" | awk -F '.' '{print $NF}')
		local end_num=$(echo "$end_ip" | awk -F '.' '{print $NF}')
		local base_ip=$(echo "$start_ip" | awk -F '.' '{print $1"."$2"."$3}')

		tmp_thread_num=5
		# 临时增加并发数
		for i in $(seq 1 "$tmp_thread_num"); do
			echo >&5
		done
		unset i

		for i in $(seq "$start_num" "$end_num"); do
			# 获取一个令牌
			read -u 5
			{
				ping -c 1 -W 2 "${base_ip}.${i}" >/dev/null 2>&1
				# 释放令牌
				echo >&5
			} &
		done
		unset i
		wait

		# 减少临时并发数
		for i in $(seq 1 "$tmp_thread_num"); do
			read -u 5
		done
		unset i

		last_scan_ip_time="$current_time"
	fi
}

# 从光猫处获取设备信息
getgateway() {
	[ -z "$gateway_info_enable" ] && return
	[ "$1" ] && [ "$1" == "reboot" ] && last_getgateway_time="$device_info_helper_sleeptime" || last_getgateway_time=$(file_date "${dir}/gateway_info")

	if [ "$last_getgateway_time" -ge "$device_info_helper_sleeptime" ]; then
		# 登录
		local loginfo=$(curl -s -L "${gateway_host_url}" -c ${dir}/cookies.txt -d "${gateway_username_id}=${gateway_username}&${gateway_password_id}=${gateway_password}") 2>/dev/null
		[ -n "$loginfo" ] && local mytoken=$(echo $loginfo | sed 's/{/\n/g' | grep token | awk '/realRestart/{print $2}' | sed $'s/\'//g')
		# 获取
		[ -n "$mytoken" ] && local get_gateway=$(curl -s -b ${dir}/cookies.txt "${gateway_info_url}" -d 'token='$mytoken | jq '.[] | iterables| "\(.ip) \(.devName) \(.model)"' | sed 's/unknown//g' | sed 's/  / /g' | sed 's/ /_/g' | sed 's/_/ /' | sed 's/\"//g')
		# 重启
		[ "$1" ] && [ "$1" == "reboot" ] && curl -s -b ${dir}/cookies.txt "${gateway_host_url}/admin/reboot" -d "token=$mytoken" >/dev/null 2>&1
		# 注销
		[ -n "$get_gateway" ] && [ -n "$gateway_logout_url" ] && curl -s -b ${dir}/cookies.txt "${gateway_logout_url}" -d 'token='$mytoken 2>/dev/null
		[ -z "$get_gateway" ] && log_change "【info】获取光猫信息失败，可能当前用户未注销或设置错误"
		# 保存信息
		[ -n "$get_gateway" ] && echo "$get_gateway" >"${dir}/gateway_info"
		gateway_iplist=$(echo "${get_gateway}" | awk '{print $1}')
	else
		unset gateway_iplist
	fi
}

# 在线设备列表
first() {
	# 注：此处为后台任务，无法操作父进程变量，故每个线程结束后都必须保存变量到文件
	# 耗时太长，定时推送不再检查
	local IPLIST=$(jq -r '.devices[] | select(.status == "online" or .status == "unknown") | .ip' "$devices_json" | sort -u)
	[ ! -f "${dir}/send_pid" ] && getgateway
	[ ! -f "${dir}/send_pid" ] && silent_run scanlocalip
	for ip in $IPLIST; do
		[ -n "$passive_mode" ] && [ "$passive_mode" -eq "1" ] && break
		# 获取一个令牌
		read -u 5
		{
			down "$ip"
			# 释放令牌
			echo >&5
		} &
	done
	wait

	local IP_INFO=$(ip addr show br-lan | grep 'inet ' | awk '{print $2}')
	[ -n "$IP_INFO" ] && local SUBNET=$(echo $IP_INFO | cut -d'/' -f1 | cut -d'.' -f1-3)
	[ -n "$SUBNET" ] && local IPLIST=$(cat /proc/net/arp | grep "0x2\|0x6" | awk '{print $1}' | grep -v "^169.254." | grep -v "^$" | sort -u | grep -oE "${SUBNET}\.[0-9]{1,3}") || local IPLIST=$(cat /proc/net/arp | grep "0x2\|0x6" | awk '{print $1}' | grep -v "^169.254." | grep -v "^$" | sort -u | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')

	local IPLIST=$(echo -e "${IPLIST}\n${gateway_iplist}\n${always_check_ip_list}" | grep -v "^$" | sort -u)
	for ip in $IPLIST; do
		[ -n "$passive_mode" ] && [ "$passive_mode" -eq "1" ] && break
		# 获取一个令牌
		read -u 5
		{
			up "$ip"
			# 释放令牌
			echo >&5
		} &
	done
	wait
}

# 检测设备上线
up() {
	local ip="$1"
	[ -z "$ip" ] && return 1

	jq -e --arg ip "$ip" '.devices[] | select(.ip == $ip and .status == "online") | .ip' "$devices_json" >/dev/null && return # 如果当前 IP 为在线，退出
	local mac=$(getmac "$ip")
	getping "$ip" "$mac" "$up_timeout" "1"
	local ping_online=$?

	# 连通
	if [ "$ping_online" -eq "0" ]; then
		silent_run LockFile lock
		local time_up=$(date +%s)

		# 如果 IP 不存在，或者 http_access 键值为空，检查 http_access 参数
		# 检查 http_access 参数时间过长，考虑只在第一次连通时检查
		#jq -e --arg ip "$ip" 'any(.devices[]; .ip == $ip and (.http_access | length == 0)) or (.devices | map(select(.ip == $ip)) | length == 0)' "$devices_json" >/dev/null && local http_access=$(check_http_access "$ip")

		# 如果是待二次离线检测 unknown 设备，json 文件中依然有信息，修改在线状态为 online
		if [ "$mac" != "unknown" ] && jq -e --arg ip "$ip" --arg mac "$mac" '.devices[] | select(.ip == $ip and .mac == $mac) | .mac' "$devices_json" >/dev/null; then
			jq --arg ip "$ip" --arg mac "$mac" '.devices |= map(if .ip == $ip then .mac = $mac | .status = "online" else . end)' "$devices_json" >"$tmp_devices_json" && mv "$tmp_devices_json" "$devices_json"
		# up
		else
			# 剔除（重置）流量数据
			usage down ${ip}
			# 删除当前 IP 对应的键值，重新写入，否则要启用 unique_by(.ip) 去重
			jq --arg ip "$ip" 'del(.devices[] | select(.ip == $ip))' "$devices_json" >"$tmp_devices_json" && mv "$tmp_devices_json" "$devices_json"
			local name=$(getname "$ip" "$mac")
			local interface=$(getinterface "ip" "$mac")
			local http_access=$(check_http_access "$ip")
			new_device='{
				"name": "'"${name}"'",
				"ip": "'"${ip}"'",
				"mac": "'"${mac}"'",
				"interface": "'"${interface}"'",
				"uptime": "'"${time_up}"'",
				"usage": "",
				"http_access": "'"${http_access}"'",
				"status": "online"
			}'
			jq --argjson newdevice "$new_device" '.devices += [$newdevice]' "$devices_json" >"$tmp_devices_json" && mv "$tmp_devices_json" "$devices_json"

			blackwhitelist ${mac}
			local ip_blackwhite=$?

			[ -f "${dir}/send_enable.lock" ] || [ -z "$notification_online" ] || [ -z "$ip_blackwhite" ] && silent_run LockFile unlock && return
			[ -z "$ip_blackwhite" ] || [ "$ip_blackwhite" -ne "0" ] && silent_run LockFile unlock && return

			[ -f "${dir}/title" ] && local title=$(cat "${dir}/title")
			[ -f "${dir}/content" ] && local content=$(cat "${dir}/content")

			if [ -z "$title" ]; then
				local title="${name} 连接了你的路由器"
				local content_title="${str_title_start} 新设备连接${str_title_end}"
			elif echo "$title" | grep -q "连接了你的路由器"; then
				local title="${name} ${title}"
			else
				local title="设备状态变化"
				local content_title="${str_title_start} 新设备连接${str_title_end}"
			fi

			local content_name="${str_linefeed}${str_tab}客户端名：${str_space}${str_space}${str_space}${str_space}${str_space}${name}"
			local content_ip="${str_linefeed}${str_tab}客户端IP：${str_space}${str_space}${str_space}${str_space}${ip}"
			local content_mac="${str_linefeed}${str_tab}客户端MAC：${str_space}${str_space}${str_space}${str_space}${mac}"
			local content_interface="${str_linefeed}${str_tab}网络接口：${str_space}${str_space}${str_space}${str_space}${str_space}${interface}"

			content="${str_splitline}${content_title}${content_name}${content_ip}${content_mac}${content_interface}"

			log_change "${disturb_text}新设备 ${name} ${ip} 连接了"

			[ -n "$title" ] && echo "$title" >"${dir}/title"
			[ -n "$content" ] && echo -n "$content" >>"${dir}/content"
		fi
		silent_run LockFile unlock
	fi
}

# 检测设备离线
down() {
	local ip="$1"
	[ -z "$ip" ] && return 1
	local mac=$(getmac "$ip")

	tmp_timeout=$down_timeout && tmp_retry_count=$timeout_retry_count
	[ -n "$only_timeout_push" ] && blackwhitelist "$mac"
	local ip_blackwhite=$? && [ "$ip_blackwhite" -ne "0" ] && tmp_timeout=10 && tmp_retry_count=2
	getping "$ip" "$mac" "$tmp_timeout" "$tmp_retry_count"
	local ping_online=$?
	silent_run LockFile lock

	# 离线，置入待验证区
	if [ "$ping_online" -eq "1" ]; then
		# 修改为 unknown
		if jq -e --arg ip "$ip" '.devices[] | select(.ip == $ip and .status == "online") | .ip' "$devices_json" >/dev/null; then
			jq --arg ip "$ip" '.devices |= map(if .ip == $ip then .status = "unknown" else . end)' "$devices_json" >"$tmp_devices_json" && mv "$tmp_devices_json" "$devices_json"
		#这里其实无需再判断，只有 status == "online" 和 status == "unknown" 的设备会使用 down $ip
		#elif jq -e --arg ip "$ip" '.devices[] | select(.ip == $ip and .status == "unknown") | .ip' "$devices_json" >/dev/null; then
		else
			jq --arg ip "$ip" '.devices |= map(if .ip == $ip then .status = "offline" else . end)' "$devices_json" >"$tmp_devices_json" && mv "$tmp_devices_json" "$devices_json"
		fi
	# 更新主机名或 MAC
	else
		local name=$(getname "$ip" "$mac")
		local interface=$(getinterface "ip" "$mac")

		if ! jq -e --arg ip "$ip" --arg mac "$mac" --arg name "$name" --arg interface "$interface" '.devices[] | select(.ip == $ip and .mac == $mac and .name == $name and .interface == $interface) | .ip' "$devices_json" >/dev/null; then
			jq --arg ip "$ip" --arg new_mac "$mac" --arg new_name "$name" --arg new_interface "$interface" '
				.devices |= map(
					if .ip == $ip then
						.mac = $new_mac |
						.name = $new_name |
						.interface = $new_interface
					else .
					end
				)
			' "$devices_json" >"$tmp_devices_json" && mv "$tmp_devices_json" "$devices_json"
		fi
	fi
	silent_run LockFile unlock
}

# 当前设备列表
current_device() {
	(echo "$lite_enable" | grep -q "content") || (echo "$lite_enable" | grep -q "device") && return
	local logrow=$(jq '.devices | map(select(.status == "online")) | length' "$devices_json")
	[ $logrow -eq "0" ] && return

	[ -f "${dir}/usage.db" ] && local ip_total_db="总计流量${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}"
	local explain="${str_splitline}${str_title_start} 现有在线设备 ${logrow} 台，具体如下${str_title_end}"
	[ $logrow -eq "0" ] && local explain="${send_content}${str_splitline}${str_title_start} 当前无在线设备${str_title_end}"
	local header="${str_linefeed}${str_tab}IP 地址${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${ip_total_db}${str_space}${str_space}${str_space}${str_space}在线时间${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}${str_space}客户端名"

	content="${explain}${header}"

	if [ -z "$defaultSortColumn" ] || [ "$defaultSortColumn" = "ip" ]; then
		# 按 IP 排序
		local IPLIST=$(jq -r '.devices[] | select(.status == "online") | .ip' "$devices_json" | sort -uV)
	else
		# 按在线时间（短到长）
		local IPLIST=$(jq -r '.devices | sort_by(-(.uptime | tonumber)) | .[] | select(.status == "online") | .ip' "$devices_json")
	fi

	local longest_ip=$(echo "$IPLIST" | awk '{ print length, $0 }' | sort -nr | head -n 1 | cut -d " " -f2-)
	#local max_length=$(("${#longest_ip}" + 1))  # +1
	local max_length="${#longest_ip}"

	for ip in $IPLIST; do
		local mac=$(getmac "$ip")
		local total=$(usage get "$mac")
		local name=$(getname "$ip" "$mac")
		local name=$(cut_str "$name" "15")
		local time_up=$(jq -r --arg ip "$ip" '.devices[] | select(.ip == $ip) | .uptime' "$devices_json")
		local time_online=$(time_for_humans $(($(date +%s) - time_up)))
		# 如果 IP 的字符长度小于 max_length，用空格补齐，方便对齐
		if [ "${#ip}" -lt "$max_length" ]; then
			local n=$(expr "$max_length" - "${#ip}")
			for i in $(seq 1 $n); do
				ip="${ip}${str_space}"
			done
			unset i n
		fi
		if [ -n "$total" ]; then
			local n=$(expr 11 - ${#total})
			for i in $(seq 1 $n); do
				total="${total}${str_space}"
			done
			unset i n
			total="${total}${str_space}|${str_space}"
		fi
		content="${content}${str_linefeed}${str_space}${ip}${str_space}|${str_space}${total}${time_online}${str_space}|${str_space}${name}"
	done
	[ -f "${dir}/send_pid" ] && echo "$content" >"$output_dir/current_device" && unset content
}

# 无人值守任务
unattended() {
	[ -z "$unattended_enable" ] || [ "$unattended_enable" -ne "1" ] && return
	[ -n "$unattended_only_on_disturb_time" ] && [ "$unattended_only_on_disturb_time" -eq "1" ] && [ -z "$sheep_starttime" ] && return
	geterrdevicealiases
	[ $? -eq "1" ] && return

	if [ -n "$unattended_autoreboot_mode" ]; then
		local interfaceuptime=$(getinterfaceuptime)
		if [ -n "$autoreboot_system_uptime" ] && [ $(cat /proc/uptime | awk -F. '{run_hour=$1/3600;printf("%d",run_hour)}') -ge "$autoreboot_system_uptime" ] && [ "$unattended_autoreboot_mode" -eq "1" ]; then
			log_change "【无人值守任务】重启路由器咯"
			cat ${logfile} >/usr/share/wechatpush/errlog
			sleep 2 && reboot && exit
		elif [ -n "$autoreboot_network_uptime" ] && [ -n "$interfaceuptime" ] && [ $(echo "$interfaceuptime" | awk -F. '{run_hour=$1/3600;printf("%d",run_hour)}') -ge "$autoreboot_network_uptime" ] && [ "$unattended_autoreboot_mode" -eq "2" ]; then
			log_change "【无人值守任务】重新拨号咯"
			ifup wan >/dev/null 2>&1
			sleep 60
		fi
	fi
}

# 设备异常流量检测
get_client_usage() {
	[ -z "$client_usage" ] && return
	[ "$client_usage" -ne "1" ] && return
	[ -z "$client_usage_max" ] && return

	[ -z "$get_client_usage_time" ] && get_client_usage_time=$(date +%s)
	(echo $client_usage_max | sed -r 's/.*(.)$/\1/' | grep -q "K\|k") && client_usage_max=$(expr ${client_usage_max%?} \* 1024)
	(echo $client_usage_max | sed -r 's/.*(.)$/\1/' | grep -q "M\|m") && client_usage_max=$(expr ${client_usage_max%?} \* 1048576)
	(echo $client_usage_max | sed -r 's/.*(.)$/\1/' | grep -q "G\|g") && client_usage_max=$(expr ${client_usage_max%?} \* 1073741824)
	[ -z "$client_usage_disturb" ] && client_usage_disturb="0"
	[ "$client_usage_disturb" -eq "0" ] && local MACLIST=$(jq -r '.devices[] | select(.status == "online") | .mac' "$devices_json" | sort -u)
	[ "$client_usage_disturb" -eq "1" ] && [ -n "$client_usage_whitelist" ] && local MACLIST=$(echo "$client_usage_whitelist")
	[ -z "$MACLIST" ] && return

	if [ "$(($(date +%s) - $get_client_usage_time))" -ge "60" ]; then
		>${dir}/client_usage_aliases
		for mac in $MACLIST; do
			echo "$mac" $(usage get ${mac} bytes) >>${dir}/client_usage_aliases
			[ -f "${dir}/old_client_usage_aliases" ] && get_client_usage_bytes=$(cat ${dir}/old_client_usage_aliases | grep -i -w $mac | awk '{print $2}' | grep -v "^$" | sort -u | head -n1) || continue

			[ -z "$get_client_usage_bytes" ] && get_client_usage_bytes="0"
			if [ "$(($(usage get ${mac} bytes) - $get_client_usage_bytes))" -ge "$client_usage_max" ]; then
				local ip=$(jq -r --arg mac "$mac" '.devices[] | select(.mac == $mac and .status == "online") | .ip' "$devices_json")
				local ip_name=$(getname ${ip} ${mac})
				local tmp_usage=$(bytes_for_humans $(expr $(usage get ${mac} bytes) - ${get_client_usage_bytes}))
				local time_up=$(jq -r --arg mac "$mac" '.devices[] | select(.mac == $mac and .status == "online") | .uptime' "$devices_json")
				local ip_total=$(usage get $mac) && [ -n "$ip_total" ] && local ip_total="${str_linefeed}${str_tab}总计流量： ${str_space}${str_space}${str_space}${str_space}${ip_total}"
				local time1=$(date +%s)
				local time1=$(time_for_humans $(expr ${time1} - ${time_up}))
				if [ -z "$title" ]; then
					title="${ip_name} 流量异常"
					local content_title="${str_splitline}${str_title_start} 设备流量异常${str_title_end}"
				elif echo "$title" | grep -q "流量异常"; then
					title="${ip_name} ${title}"
				else
					title="设备状态变化"
					local content_title="${str_splitline}${str_title_start} 设备流量异常${str_title_end}"
				fi

				local content_name="${str_linefeed}${str_tab}客户端名：${str_space}${str_space}${str_space}${str_space}${str_space}${ip_name}"
				local content_ip="${str_linefeed}${str_tab}客户端IP：${str_space}${str_space}${str_space}${str_space}${ip}"
				local content_mac="${str_linefeed}${str_tab}客户端MAC：${str_space}${str_space}${str_space}${str_space}${mac}${ip_total}"
				local content_usage="${str_linefeed}${str_tab}一分钟内流量：${str_space}${str_space}${str_space}${tmp_usage}"
				local content_time="${str_linefeed}${str_tab}在线时间：${str_space}${str_space}${str_space}${str_space}${time1}"

				content="${content}${content_title}${content_name}${content_ip}${content_mac}${content_usage}${content_time}"
			fi
		done
		cat ${dir}/client_usage_aliases >${dir}/old_client_usage_aliases
		get_client_usage_time=$(date +%s)
	fi
}

# ------------------------------------
# 自动封禁相关
#

# 添加白名单，懒得写删除项和信息显示了，纯粹就是懒
add_ip_white() {
	[ -n "$port_knocking_enable" ] && [ "$port_knocking_enable" -eq "1" ] || return
	[ -z "$2" ] && timeout=$login_ip_white_timeout || timeout=$2
	# 检查 IP 版本
	unset ipset_name
	(echo "$1" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$') && local ipset_name="wechatpush_whitelist"
	(echo "$1" | grep -Eq '^([\da-fA-F0-9]{1,4}(:{1,2})){1,15}[\da-fA-F0-9]{1,4}$') && local ipset_name="wechatpush_whitelistv6"
	[ -z "$ipset_name" ] && log_change "【！！！】白名单添加失败，IP 格式错误" && return

	[ -n "$nftables_version" ] && {
		nft delete element inet fw4 $ipset_name { $1 } >/dev/null 2>&1
		nft add element inet fw4 $ipset_name { $1 expires ${timeout}s } #没找到刷新时间的命令，删除再添加
	} || {
		ipset -exist add $ipset_name $1 timeout $timeout
	}
}

# 初始化白名单
init_ip_white() {
	[ -n "$port_knocking_enable" ] && [ "$port_knocking_enable" -eq "1" ] || return
	# 设置 IP 版本变量
	if [ $1 == "ipv4" ]; then
		ipset_name="wechatpush_whitelist"
		ip_version="ip"
	elif [ $1 == "ipv6" ]; then
		ipset_name="wechatpush_whitelistv6"
		ip_version="ip6"
		nat_table_cmd="family inet6"
	fi

	if [ -n "$nftables_version" ]; then
		! nft list set inet fw4 $ipset_name >/dev/null 2>&1 && nft add set inet fw4 $ipset_name { type ${1}_addr\; flags timeout\; timeout ${login_ip_white_timeout}s\; }
		nft -- add chain inet fw4 wechatpush_dstnat { type nat hook prerouting priority -100 \; }
		nft add chain inet fw4 wechatpush_srcnat { type nat hook postrouting priority 100 \; }
	else
		! ipset list $ipset_name >/dev/null 2>&1 && ipset create $ipset_name hash:ip timeout $login_ip_white_timeout $nat_table_cmd >/dev/null 2>&1
	fi

	# 端口放行
	if [ -n "$login_port_white" ]; then
		local login_port_white=$(echo "$login_port_white" | sed 's/ //g' | sed 's/,/, /g') 2>/dev/null
		if [ -n "$nftables_version" ]; then
			local count_accept_rules=$(nft list ruleset | grep -c "tcp dport.* ${login_port_white}.* $ip_version saddr @${ipset_name} counter packets .* accept comment \"\!wechatpush Accept rule\"")
			if [ $count_accept_rules -eq 0 ]; then
				nft insert rule inet fw4 input tcp dport { $login_port_white } $ip_version saddr @$ipset_name counter accept comment \"\!wechatpush Accept rule\" >/dev/null 2>&1
			elif [ $count_accept_rules -ne 1 ]; then
				local i=0
				local handles=$(nft --handle list ruleset | grep "\!wechatpush Accept rule" | grep -v "tcp dport.* ${login_port_white}.* $ip_version saddr @${ipset_name} counter packets .* accept comment \"\!wechatpush Accept rule\"" | awk '{print $NF}')
				for handle in $handles; do
					[ $i -eq 0 ] && i=1 && continue
					nft delete rule $handle
				done
			fi
		else
			${ip_version}tables -C INPUT -m set --match-set $ipset_name src -p tcp -m multiport --dport $login_port_white -j ACCEPT >/dev/null 2>&1 || ${ip_version}tables -I INPUT -m set --match-set $ipset_name src -p tcp -m multiport --dport $login_port_white -j ACCEPT >/dev/null 2>&1
		fi
	fi
	unset handle
	# 端口转发
	while IFS= read -r port_forward; do
		port_forward=$(echo "$port_forward" | sed 's/,/ /g') 2>/dev/null
		[ $(echo $port_forward | awk -F" " '{print NF}') -ne "4" ] && continue
		local src_ip=$(echo ${port_forward} | awk '{print $1}')
		local src_port=$(echo ${port_forward} | awk '{print $2}')
		local dst_ip=$(echo ${port_forward} | awk '{print $3}')
		local dst_port=$(echo ${port_forward} | awk '{print $4}')
		if [ -n "$nftables_version" ]; then
			! nft list ruleset | grep "$ip_version saddr @${ipset_name} tcp dport $src_port counter .* dnat $ip_version to $dst_ip:$dst_port comment \"\!wechatpush DNAT rule\"" >/dev/null 2>&1 && nft insert rule inet fw4 wechatpush_dstnat meta nfproto $1 $ip_version saddr @${ipset_name} tcp dport $src_port counter dnat to "$dst_ip:$dst_port" comment \"\!wechatpush DNAT rule\" >/dev/null 2>&1
			! nft list ruleset | grep "$ip_version daddr $dst_ip tcp dport $dst_port counter .* snat $ip_version to $src_ip comment \"\!wechatpush SNAT rule\"" >/dev/null 2>&1 && nft insert rule inet fw4 wechatpush_srcnat $ip_version daddr $dst_ip tcp dport $dst_port counter snat to $src_ip comment \"\!wechatpush SNAT rule\" >/dev/null 2>&1
		else
			${ip_version}tables -t nat -C PREROUTING -m set --match-set $ipset_name src -p tcp --dport $src_port -j DNAT --to-destination "$dst_ip:$dst_port" >/dev/null 2>&1 || ${ip_version}tables -t nat -I PREROUTING -m set --match-set $ipset_name src -p tcp --dport $src_port -j DNAT --to-destination "$dst_ip:$dst_port" >/dev/null 2>&1
			${ip_version}tables -t nat -C POSTROUTING -m set --match-set $ipset_name src -p tcp -d $dst_ip --dport $dst_port -j SNAT --to-source $src_ip >/dev/null 2>&1 || ${ip_version}tables -t nat -I POSTROUTING -m set --match-set $ipset_name src -p tcp -d $dst_ip --dport $dst_port -j SNAT --to-source $src_ip >/dev/null 2>&1
		fi
	done <<<"$login_port_forward_list"
	unset port_forward
}

# 初始化黑名单规则
init_ip_black() {
	[ -n "$login_web_black" ] && [ "$login_web_black" -eq "1" ] || return
	# 设置 IP 版本变量
	if [ $1 == "ipv4" ]; then
		ipset_name="wechatpush_blacklist"
		ip_version="ip"
	elif [ $1 == "ipv6" ]; then
		ipset_name="wechatpush_blacklistv6"
		ip_version="ip6"
		nat_table_cmd="family inet6"
	fi

	[ -n "$nftables_version" ] && {
		! nft list set inet fw4 ${ipset_name} >/dev/null 2>&1 && nft add set inet fw4 ${ipset_name} { type ${1}_addr\; flags timeout\; timeout ${login_ip_black_timeout}s\; }
		! nft list ruleset | grep "$ip_version saddr @${ipset_name} counter .* comment \"\!wechatpush Drop rule\"" >/dev/null 2>&1 && nft insert rule inet fw4 input $ip_version saddr @${ipset_name} counter drop comment \"\!wechatpush Drop rule\" >/dev/null 2>&1
	} || {
		ipset list $ipset_name >/dev/null 2>&1 || ipset create ${ipset_name} hash:ip timeout ${login_ip_black_timeout} ${nat_table_cmd} >/dev/null 2>&1
		${ip_version}tables -C INPUT -m set --match-set ${ipset_name} src -j DROP >/dev/null 2>&1 || ${ip_version}tables -I INPUT -m set --match-set ${ipset_name} src -j DROP >/dev/null 2>&1
	}
}

# 添加黑名单
add_ip_black() {
	local login_ip=$1
	[ -z "$login_ip" ] && return 1
	echo "$login_ip_white_list" | grep -w -q "$login_ip" && return 1
	# 检查 IP 版本
	unset ipset_name
	(echo "$login_ip" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$') && ipset_name="wechatpush_blacklist"
	(echo "$login_ip" | grep -Eq '^([\da-fA-F0-9]{1,4}(:{1,2})){1,15}[\da-fA-F0-9]{1,4}$') && ipset_name="wechatpush_blacklistv6"
	[ -z "$ipset_name" ] && log_change "【！！！】黑名单添加失败，IP 格式错误" && return 1

	! cat "$ip_blacklist_path" | grep -q -w -i $login_ip && echo "$login_ip timeout $login_ip_black_timeout" >>"$ip_blacklist_path"

	[ -n "$nftables_version" ] && {
		nft list set inet fw4 ${ipset_name} | grep -qw "${login_ip}" && return 1 # IP 已存在
		nft add element inet fw4 $ipset_name { $login_ip expires ${login_ip_black_timeout}s } >/dev/null 2>&1
	} || {
		ipset -exist add $ipset_name $login_ip timeout ${login_ip_black_timeout} >/dev/null 2>&1
	}
}

# 移出黑名单
del_ip_black() {
	[ -z "$1" ] && return
	sed -i "/^${1}/d" ${ip_blacklist_path}

	# 检查 IP 版本
	unset ipset_name
	(echo "$1" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$') && ipset_name="wechatpush_blacklist"
	(echo "$1" | grep -Eq '^([\da-fA-F0-9]{1,4}(:{1,2})){1,15}[\da-fA-F0-9]{1,4}$') && ipset_name="wechatpush_blacklistv6"
	[ -z "$ipset_name" ] && log_change "【！！！】黑名单删除失败，IP 格式错误" && return

	[ -n "$nftables_version" ] && {
		nft delete element inet fw4 ${ipset_name} { $1 } >/dev/null 2>&1
	} || {
		ipset list ${ipset_name} >/dev/null 2>&1 && ipset -! del ${ipset_name} ${1}
	}
}

# 设置防火墙列表
set_ip_black() {
	# 检查换行，避免出错
	[ $(tail -n1 "${ip_blacklist_path}" | wc -l) -eq "0" ] && echo -e >>${ip_blacklist_path}

	# 从 ip_blacklist 文件逐行添加黑名单，add_ip_black() 处验证是否重复，此处不在验证
	for ip_black in $(cat ${ip_blacklist_path} | awk '{print $1}'); do
		add_ip_black "$ip_black"
	done
	# 当 ip_blacklist 文件清除 IP 时，从集合中清除 IP
	[ -n "$nftables_version" ] && fw_info_blacklist=$(nft list set inet fw4 wechatpush_blacklist | tr -d '\n' | grep -oE 'elements = \{[^}]*\}' | grep -oE '[^{}]+ expires [^,}]+[,\}]' | tr ',}' '\n' | tr -s ' ' | sed -e 's/^[[:space:]]*//')
	[ -n "$nftables_version" ] && fw_info_blacklistv6=$(nft list set inet fw4 wechatpush_blacklistv6 | tr -d '\n' | grep -oE 'elements = \{[^}]*\}' | grep -oE '[^{}]+ expires [^,}]+[,\}]' | tr ',}' '\n' | tr -s ' ' | sed -e 's/^[[:space:]]*//')
	[ -z "$nftables_version" ] && fw_info_blacklist=$(ipset list wechatpush_blacklist | grep "timeout" 2>/dev/null)
	[ -z "$nftables_version" ] && fw_info_blacklistv6=$(ipset list wechatpush_blacklistv6 | grep "timeout" 2>/dev/null)

	[ -n "$fw_info_blacklist" ] && [ -n "$fw_info_blacklistv6" ] && combined_fw_info_blacklist="${fw_info_blacklist}\n${fw_info_blacklistv6}"
	[ -z "$fw_info_blacklist" ] && combined_fw_info_blacklist="${fw_info_blacklistv6}" || combined_fw_info_blacklist="${fw_info_blacklist}"

	while IFS= read -r ip_black_info; do
		ip_black=$(echo "$ip_black_info" | grep -Eo "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}")
		[ -z "$ip_black" ] && ip_black=$(echo "$ip_black_info" | grep -Eo "([\da-fA-F0-9]{1,4}(:{1,2})){1,15}[\da-fA-F0-9]{1,4}")
		[ -z "$ip_black" ] && continue
		cat ${ip_blacklist_path} | grep -q -w -i ${ip_black} && sed -i "/^${ip_black}/d" ${ip_blacklist_path} && echo ${ip_black_info} >>${ip_blacklist_path} || del_ip_black ${ip_black}
	done <<<"$combined_fw_info_blacklist"
}

# 监听登录事件（封装为函数，拒绝调试模式下日志刷屏）
monitor_logins() {
	if [ -n "$web_logged" ] || [ -n "$ssh_logged" ] || [ -n "$web_login_failed" ] || [ -n "$ssh_login_failed" ]; then
		# 声明关联数组
		declare -A web_login_counts
		declare -A ssh_login_counts
		declare -A web_failed_counts
		declare -A ssh_failed_counts

		# 子进程的信号处理函数
		cleanup_child() {
			kill $child_pid >/dev/null 2>&1
			wait $child_pid >/dev/null 2>&1
			rm -f "${dir}/child_pid" >/dev/null 2>&1
			exit 0
		}

		# 设置信号处理
		trap cleanup_child SIGINT SIGTERM

		(
			# 监听系统日志，-f 表示跟随实时日志，-p 表示日志级别为 notice
			logread -f -p notice | while IFS= read -r line; do
				[ -n "$web_logged" ] && {
					web_login_ip=$(echo "$line" | grep -i "accepted login" | awk '{print $NF}')
					[ -n "$web_login_ip" ] && process_login "$web_login_ip" $(echo "$line" | awk '{print $4}') web_login_counts
				}

				[ -n "$ssh_logged" ] && {
					ssh_login_ip=$(echo "$line" | grep -i "Password auth succeeded\|Pubkey auth succeeded" | awk '{print $NF}' | sed -nr 's#^(.*):.[0-9]{1,5}#\1#gp' | sed -e 's/%.*//')
					[ -n "$ssh_login_ip" ] && process_login "$ssh_login_ip" $(echo "$line" | awk '{print $4}') ssh_login_counts
				}

				[ -n "$web_login_failed" ] && {
					web_failed_ip=$(echo "$line" | grep -i "failed login" | awk '{print $NF}')
					[ -n "$web_failed_ip" ] && process_login "$web_failed_ip" $(echo "$line" | awk '{print $4}') web_failed_counts
				}

				[ -n "$ssh_login_failed" ] && {
					# 匹配特定的 SSH 登录失败情况并提取 IP 地址和时间
					ssh_failed_ip=$(echo "$line" | grep -iE "Bad password attempt|Login attempt for nonexistent user|Max auth tries reached" | awk '{print $NF}' | sed -nr 's#^(.*):[0-9]{1,5}#\1#gp' | sed -e 's/%.*//')

					# 如果未能提取到 IP，从日志标识符提取失败用户的 ID，并再次提取 IP
					if [ -z "$ssh_failed_ip" ]; then
						ssh_failed_num=$(echo "$line" | sed -n 's/.*authpriv\.warn dropbear\[\([0-9]\+\)\]: Login attempt for nonexistent user/\1/p')
						[ -n "$ssh_failed_num" ] && ssh_failed_ip=$(logread notice | grep "authpriv\.info dropbear\[${ssh_failed_num}\].*Child connection from" | awk '{print $NF}' | sed -nr 's#^(.*):[0-9]{1,5}#\1#gp' | sed -e 's/%.*//' | tail -n 1)
					fi

					# 如果成功提取到 IP 地址，调用 process_login 处理
					[ -n "$ssh_failed_ip" ] && process_login "$ssh_failed_ip" $(echo "$line" | awk '{print $4}') ssh_failed_counts
				}

			done
		) &
		# 分离子shell，避免影响 wait
		child_pid=$!
		sleep 1
		disown "$child_pid"
		echo $child_pid >"${dir}/child_pid"
	fi
}

# 处理登录事件
# 参数:
#   $1: IP
#   $2: 日志时间 - 从日志中读取而不是使用当前时间，避免秒对应不上
#   $3: 数组名 - 记录 IP 和登录次数的关联数组名
process_login() {
	local login_ip=$1
	local login_time=$2
	local -n login_counts=$3

	# 如果数组中不存在此 IP，初始化为 0
	if [ -z "${login_counts["$login_ip"]}" ]; then
		login_counts["$login_ip"]=0
	fi
	# +1
	login_counts["$login_ip"]=$((login_counts["$login_ip"] + 1))
	local count=${login_counts["$login_ip"]}

	# 封禁
	if [[ $count -ge $login_max_num && ("$3" == "web_failed_counts" || "$3" == "ssh_failed_counts") ]]; then
		add_ip_black ${login_ip} && {
			unset login_counts["$login_ip"]
			login_send "$login_ip" "$login_time" "$3"
		}
	fi

	# 正常登录
	if [[ "$3" == "web_login_counts" || "$3" == "ssh_login_counts" ]]; then
		add_ip_white ${login_ip}
		del_ip_black ${login_ip} # 白名单已经优先于黑名单，但白名单集合有超时限制，防止下次修改代码忘记，上保险
		unset web_failed_counts["$login_ip"]
		unset ssh_failed_counts["$login_ip"]
		unset login_counts["$login_ip"]
		login_send "$login_ip" "$login_time" "$3"
	fi
	[ "${#login_counts[@]}" -gt "100" ] && login_counts=("${login_counts[@]: -100}")
}

# ------------------------------------
# 信息推送相关
#
# 发送定时数据
send() {
	pid=$$
	echo "$pid" >"${dir}/send_pid"
	log_change "【定时数据】创建定时任务"
	>"${dir}/send_enable.lock"
	disturb
	disturb_RETVAL=$?
	get_config "send_title" "send_notification"

	[ -z "$1" ] && cpuload=$(getcpu)
	[ -z "$1" ] && service_status=$(ubus call service list '{"name": "wechatpush"}' | grep -o '"running": true')
	# 只有当主程序没有运行，且定时任务中选择了推送设备列表，免打扰选项中也没屏蔽设备列表时，才进行在线设备扫描
	[ -z "$1" ] && [ -z "$service_status" ] && echo "$send_notification" | grep -q "client_list" && echo "$lite_enable" | grep -v -q "device" && first &
	if [ -z "$1" ] && (echo "$send_notification" | grep -q "router_status"); then
		cat /proc/loadavg | awk '{print $1" "$2" "$3}' >"$output_dir/systemload" &
		free -m | sed -n '2p' | awk '{printf "%.2f%%\n",($3/$2)*100}' >"$output_dir/ramload" &
		curl -o /dev/null --connect-timeout 5 -s -w %{http_code} www.google.com >"$output_dir/Qwai" &
		cat /proc/uptime | awk -F. '{run_days=$1 / 86400;run_hour=($1 % 86400)/3600;run_minute=($1 % 3600)/60;run_second=$1 % 60;printf("运行时间：%d天%d时%d分%d秒",run_days,run_hour,run_minute,run_second)}' >"$output_dir/systemstatustime" &
		systeminfo_enable="1"
	fi
	[ -z "$1" ] && (echo "$send_notification" | grep -q "router_temp") && soc_temp >"$output_dir/cputemp" &

	if [ -z "$1" ] && (echo "$send_notification" | grep -q "wan_info"); then
		getip wanipv4 >"$output_dir/send_wanIP" &
		getip hostipv4 | jq -r '.IP' >"$output_dir/send_hostIP" &
		waninfo_enable="1"
		if [ "$get_ipv6_mode" -ne "0" ]; then
			getip wanipv6 >"$output_dir/send_wanIPv6" &
			getip hostipv6 | jq -r '.IP' >"$output_dir/send_hostIPv6" &
			ipv6_enable="1"
		fi
		getinterfaceuptime >"$output_dir/interfaceuptime" &
	fi

	#[ -z "$1" ] && ( echo "$send_notification"|grep -q "disk_info" ) && get_disk &
	[ -z "$1" ] && get_disk &

	[ -z "$1" ] && send_title="路由状态："
	[ -n "$1" ] && send_title="发送测试：" && send_content="${str_splitline}${str_title_start}内容1${str_title_end}${str_linefeed}${str_tab}设备1${str_linefeed}${str_tab}设备2${str_splitline}${str_title_start}内容2${str_title_end}${str_linefeed}${str_tab}设备3${str_linefeed}${str_tab}设备4"

	wait_and_cat
	[ -z "$1" ] && (echo "$send_notification" | grep -q "client_list") && current_device & # 设备列表需等待 first & 完成

	if [ -z "$1" ] && [ -n "$systeminfo_enable" ]; then
		[[ $Qwai -eq 200 || $Qwai -eq 301 || $Qwai -eq 302 ]] && Qwai_status="已连通！" || Qwai_status="已断开！"
		send_content="${send_content}${str_splitline}${str_title_start} 系统运行状态${str_title_end}"
		send_content="${send_content}${str_linefeed}${str_tab}平均负载：${systemload}"
		send_content="${send_content}${str_linefeed}${str_tab}CPU占用：${cpuload}"
		send_content="${send_content}${str_linefeed}${str_tab}内存占用：${ramload}"
		send_content="${send_content}${str_linefeed}${str_tab}全球互联：${Qwai_status}"
		send_content="${send_content}${str_linefeed}${str_tab}${systemstatustime}"
	fi

	if [ -z "$1" ] && (echo "$send_notification" | grep -q "router_temp"); then
		[ -n "$cputemp" ] && send_content="${send_content}${str_splitline}${str_title_start} 设备温度${str_title_end}${str_linefeed}${str_tab}CPU：${cputemp}℃"
		[ -z "$cputemp" ] && send_content="${send_content}${str_splitline}${str_title_start} 设备温度${str_title_end}${str_linefeed}${str_tab}无法获取设备温度"
	fi

	#if [ -z "$1" ] && ( echo "$send_notification"|grep -q "disk_info" ); then
	if [ -z "$1" ]; then
		send_content="${send_content}${get_disk}"
	fi

	if [ -z "$1" ] && [ -n "$waninfo_enable" ]; then
		send_content="${send_content}${str_splitline}${str_title_start} WAN 口信息${str_title_end}"
		if [ "$send_wanIP" == "$send_hostIP" ]; then
			send_content="${send_content}${str_linefeed}${str_tab}IPv4: ${send_wanIP}"
		elif [ "$get_ipv4_mode" -eq "1" ]; then
			send_content="${send_content}${str_linefeed}${str_tab}接口 IPv4: ${send_wanIP}"
			[ -n "$send_hostIP" ] && send_content="${send_content}${str_linefeed}${str_tab}外网 IPv4: ${send_hostIP}"
		elif [ "$get_ipv4_mode" -eq "2" ]; then
			[ -n "$send_wanIP" ] && send_content="${send_content}${str_linefeed}${str_tab}接口 IPv4: ${send_wanIP}"
			send_content="${send_content}${str_linefeed}${str_tab}外网 IPv4: ${send_hostIP}"
		fi
		if [ -n "$ipv6_enable" ]; then
			if [ "$send_wanIPv6" == "$send_hostIPv6" ]; then
				send_content="${send_content}${str_linefeed}${str_tab}IPv6: ${send_wanIPv6}"
			elif [ "$get_ipv6_mode" -eq "1" ]; then
				send_content="${send_content}${str_linefeed}${str_tab}接口 IPv6: ${send_wanIPv6}"
				[ -n "$send_hostIPv6" ] && send_content="${send_content}${str_linefeed}${str_tab}外网 IPv6: ${send_hostIPv6}"
			elif [ "$get_ipv6_mode" -eq "2" ]; then
				[ -n "$send_wanIPv6" ] && send_content="${send_content}${str_linefeed}${str_tab}接口 IPv6: ${send_wanIPv6}"
				send_content="${send_content}${str_linefeed}${str_tab}外网 IPv6: ${send_hostIPv6}"
			fi
		fi
		interfaceuptime=$(getinterfaceuptime)
		[ -n "$interfaceuptime" ] && wanstatustime=$(printf "在线时间：%d天%d时%d分%d秒" $((interfaceuptime / 86400)) $(((interfaceuptime % 86400) / 3600)) $(((interfaceuptime % 3600) / 60)) $((interfaceuptime % 60)))
		send_content="${send_content}${str_linefeed}${str_tab}${wanstatustime}"
	fi

	if [ -z "$1" ] && (echo "$send_notification" | grep -q "client_list"); then
		wait_and_cat
		send_content="${send_content}${current_device}"
	fi

	[ -n "$device_name" ] && send_title="【$device_name】${send_title}"
	[ -z "$send_content" ] && send_content="${str_splitline}${str_title_start} 我遇到了一个难题${str_title_end}${str_linefeed}${str_tab}定时发送选项错误，你没有选择需要发送的项目，该怎么办呢${str_splitline}"
	[ "$disturb_RETVAL" -eq "0" ] && diy_send "${send_title}" "${send_content}" "${jsonpath}" "$1" >/dev/null 2>&1
	RETVAL=$?
	[ $RETVAL -eq 1 ] && [ "$send_disturb" -eq "0" ] && log_change "【！！！】定时推送失败，请检查网络或设置信息" || log_change "${disturb_text}定时推送任务完成"
	deltemp
	rm -f "${dir}/send_pid"
	return $RETVAL
}

# 登录提醒通知
login_send() {
	local login_ip=$1
	local login_time=$2
	local log_type=$3

	local login_title
	local login_content

	>"${dir}/send_enable.lock"

	[ -z "$login_ip" ] && return
	echo "$login_ip_white_list" | grep -w -q "$login_ip" && [ -n "$login_log_enable" ] && return

	[[ "$log_type" == "web"* ]] && local log_type_short="Web" || local log_type_short="SSH"
	if [ -n "$login_disturb" ] && [ "$login_disturb" -eq 2 ]; then
		[ -f "$logfile" ] && login_log=$(grep -w "$login_ip" "$logfile" | grep -v "\【info\】" | tail -n 1)
		[ -n "$login_log" ] && log_timestamp=$(date -d "$(echo "$login_log" | awk '{print $1, $2}')" +%s) || log_timestamp=0
		[ $(($(date +%s) - log_timestamp)) -lt $login_notification_delay ] && local log_only="1" && [ -n "$login_log_enable" ] && return
	fi
	[ -n "$log_only" ] && log_change "【info】设备 ${login_ip} 通过 ${log_type_short} 登录了路由器 " && return

	# 查询 IP 归属地
	local login_ip_attribution=$(get_ip_attribution "${login_ip}")
	# 登录方式
	if [[ "$log_type" == "web"* ]]; then
		# Web 登录、非法登录
		local login_mode=$(logread notice | grep -E ".* $login_time.*$login_ip.*" | awk '{print $13}' | tail -n 1)
		[ "$login_mode" = "/" ] && login_mode="/ (首页登录)"
	elif [[ "$log_type" == "ssh_login"* ]]; then
		# SSH 登录
		local login_mode=$(logread notice | grep -E ".* $login_time.*$login_ip.*" | awk '{print $8}' | tail -n 1)
	else
		local login_mode=$(logread notice | grep -E ".* $login_time.*$login_ip.*" | awk '{for(i=8;i<NF;i++) if($i=="from") break; else printf $i " "}' | tail -n 1)
	fi

	if [ -z "$login_disturb" ] || [ "$login_disturb" -ne "1" ]; then
		if [[ "$log_type" == *"failed"* ]]; then
			local log_message="频繁尝试登录"
			local login_title="${login_ip} ${log_message}"
			local login_content_info="${str_splitline}${str_title_start} 封禁信息${str_title_end}"
		else
			local log_message="登录了路由器"
			local login_title="${login_ip} ${log_message}"
			local login_content_info="${str_splitline}${str_title_start} 登录信息${str_title_end}"
		fi

		log_change "${disturb_text}设备 ${login_ip} (${login_ip_attribution}) 通过 ${log_type_short} ${login_mode} ${log_message}"

		local login_content_time="${str_linefeed}${str_tab}时间：${str_space}${str_space}${str_space}${str_space}${login_time}"
		local login_content_ip="${str_linefeed}${str_tab}设备 IP：${str_space}${str_space}${login_ip}"
		[ -n "$login_ip_attribution" ] && local login_content_attribution="${str_linefeed}${str_tab}归属地：${str_space}${str_space}${login_ip_attribution}"
		local login_content_mode="${str_linefeed}${str_tab}登录方式：${str_space}${log_type_short} ${login_mode}"

		login_content="${login_content_info}${login_content_time}${login_content_ip}${login_content_attribution}${login_content_mode}"
	fi

	# 发送通知
	disturb
	disturb_RETVAL=$?
	[ -z "$login_title" ] && return
	[ -n "$device_name" ] && login_title="【$device_name】$login_title"
	(echo "$lite_enable" | grep -q "login_content") && login_content="$login_title"
	[ "$disturb_RETVAL" -eq 0 ] && [ -n "$login_title" ] && diy_send "${login_title}" "${login_content}" "${jsonpath}" >/dev/null 2>&1
}

# 设备离线通知
down_send() {
	# 如果没有离线设备，退出
	[ "$(jq '.devices | map(select(.status == "offline")) | length' "$devices_json")" -eq 0 ] && return

	local IPLIST=$(jq -r '.devices[] | select(.status == "offline") | .ip' "$devices_json" | sort -u)
	for ip in $IPLIST; do
		local mac=$(getmac "$ip")
		blackwhitelist "$mac"
		local ip_blackwhite=$?
		[ -z "$notification_offline" ] || [ -z "$ip_blackwhite" ] && continue
		[ -z "$ip_blackwhite" ] || [ "$ip_blackwhite" -ne "0" ] && continue

		local name=$(getname "$ip" "$mac")
		local time_up=$(jq -r --arg ip "$ip" '.devices[] | select(.ip == $ip and .status == "offline") | .uptime' "$devices_json")
		local ip_total=$(usage get "$mac")
		[ -n "$ip_total" ] && ip_total="${str_linefeed}${str_tab}总计流量： ${str_space}${str_space}${str_space}${str_space}${ip_total}"
		local time1=$(date +%s)
		local time1=$(time_for_humans $(expr ${time1} - ${time_up}))

		if [ -z "$title" ]; then
			title="${name} 断开连接"
			content_title="${str_splitline}${str_title_start} 设备断开连接${str_title_end}"
		elif echo "$title" | grep -q "断开连接"; then
			title="${name} ${title}"
			content_title=""
		else
			title="设备状态变化"
			content_title="${str_splitline}${str_title_start} 设备断开连接${str_title_end}"
		fi

		local content_name="${str_linefeed}${str_tab}客户端名：${str_space}${str_space}${str_space}${str_space}${str_space}${name}"
		local content_ip="${str_linefeed}${str_tab}客户端IP：${str_space}${str_space}${str_space}${str_space}${ip}"
		local content_mac="${str_linefeed}${str_tab}客户端MAC：${str_space}${str_space}${str_space}${str_space}${mac}${ip_total}"
		local content_time="${str_linefeed}${str_tab}在线时间：${str_space}${str_space}${str_space}${str_space}${time1}"

		content="${content}${content_title}${content_name}${content_ip}${content_mac}${content_time}"

		log_change "${disturb_text}设备 ${name} ${ip} 断开连接 "
	done
	silent_run LockFile lock
	jq 'del(.devices[] | select(.status == "offline"))' "$devices_json" >"$tmp_devices_json" && mv "$tmp_devices_json" "$devices_json"
	silent_run LockFile unlock
}

# 推送
diy_send() {
	[ -z "$jsonpath" ] && return
	(! echo "$lite_enable" | grep -q "content") && (! echo "$lite_enable" | grep -q "nowtime") && local nowtime=$(date "+%Y-%m-%d %H:%M:%S")
	! jq -r '.' ${3} >/dev/null 2>&1 && log_change "【！！！】json 文件格式错误，这不是一个标准的 json 文件，请检查 ${3} 文件是否有特殊符号未转义或语法错误" && return 1
	local diyurl=$(jq -r .url ${3}) && local diyurl=$(eval echo ${diyurl})
	local type=$(jq -r '.type' ${3}) && local type=$(eval echo ${type})
	local data=$(jq -r '.data' ${3}) && local data=$(eval echo ${data})
	local content_type=$(jq -r '.content_type' ${3})
	! jq "$type" ${3} >${tempjsonpath} && log_change "【！！！】type:{ } 字段转义变量后格式错误，请检查 type:{ } 字段内是否有特殊符号未转义或语法错误" && return 1

	[ -n "$proxy_address" ] && local proxy_cmd="-x $proxy_address"
	curl $proxy_cmd --connect-timeout 30 --max-time 60 --retry 2 -X POST -H "$content_type" -d "$data" "${diyurl}"
	local RETVAL=$?

	if [ $RETVAL -ne 0 ] || [[ -n "$4" && "$4" == "test" ]]; then
		[ $RETVAL -ne 0 ] && log_change "【！！！】网络错误或 URL 错误，推送失败，curl 返回值为 ${RETVAL}，请参考如下调试信息进行排查"
		[ -n "$4" ] && log_change "【debug】推送测试已开启，当前 curl 返回值为 ${RETVAL}"
		echo '{"url":"'${diyurl}'","content_type":"'${content_type}'","type":'$(jq "$type" ${3})'}' >${dir}/debug_send_json
		echo -e "${send_title}" "${send_content}" >${dir}/debug_send_content
		cat ${tempjsonpath} >${dir}/debug_send_data
		! jq -r '.' ${dir}/debug_send_json && log_change "【！！！】转义变量后格式错误，请检查 ${dir}/debug_send_json 字段内是否有特殊符号未转义或语法错误"
		log_change "【debug】json 文件已保存至：${dir}/debug_send_json"
		log_change "【debug】推送内容预览文件保存至：${dir}/debug_send_content"
		log_change "【debug】如果收不到信息，请检查 ${dir}/debug_send_data 文件，或使用下列命令手动测试返回值"
		log_change "【debug】curl -X POST -H \"$content_type\" -d \"@${dir}/debug_send_data\" \"${diyurl}\""
		return 1
	else
		return 0
	fi
}

# ------------------------------------
main "$@"
